github.com/GoogleCloudPlatform/testgrid@v0.0.174/util/queue/queue_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 queue
    18  
    19  import (
    20  	"container/heap"
    21  	"context"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	"github.com/sirupsen/logrus"
    28  	"google.golang.org/protobuf/testing/protocmp"
    29  )
    30  
    31  func TestInit(t *testing.T) {
    32  	now := time.Now()
    33  	log := logrus.WithField("test", "TestInit")
    34  	cases := []struct {
    35  		name  string
    36  		q     *Queue
    37  		names []string
    38  		when  time.Time
    39  
    40  		next []string
    41  	}{
    42  		{
    43  			name: "add",
    44  			q:    &Queue{},
    45  			names: []string{
    46  				"hi",
    47  			},
    48  			when: now,
    49  
    50  			next: []string{"hi"},
    51  		},
    52  		{
    53  			name: "remove",
    54  			q: func() *Queue {
    55  				var q Queue
    56  				q.Init(log, []string{"drop", "keep"}, now)
    57  				return &q
    58  			}(),
    59  			names: []string{
    60  				"keep",
    61  				"add",
    62  			},
    63  			when: now.Add(-time.Minute),
    64  			next: []string{
    65  				"add",
    66  				"keep",
    67  			},
    68  		},
    69  	}
    70  
    71  	for _, tc := range cases {
    72  		t.Run(tc.name, func(t *testing.T) {
    73  			tc.q.Init(log, tc.names, tc.when)
    74  
    75  			var got []string
    76  			for range tc.next {
    77  				got = append(got, heap.Pop(&tc.q.queue).(*item).name)
    78  			}
    79  			if diff := cmp.Diff(tc.next, got, protocmp.Transform()); diff != "" {
    80  				t.Errorf("FixAll() got unexpected diff (-want +got):\n%s", diff)
    81  			}
    82  		})
    83  	}
    84  }
    85  
    86  func TestFixAll(t *testing.T) {
    87  	log := logrus.WithField("test", "TestFixAll")
    88  	now := time.Now()
    89  	cases := []struct {
    90  		name  string
    91  		q     *Queue
    92  		fixes map[string]time.Time
    93  		later bool
    94  
    95  		next []string
    96  		err  bool
    97  	}{
    98  		{
    99  			name: "empty",
   100  			q:    &Queue{},
   101  		},
   102  		{
   103  			name: "later",
   104  			q: func() *Queue {
   105  				var q Queue
   106  				q.Init(log, []string{
   107  					"first-now-second",
   108  					"second-now-fifth",
   109  					"third",
   110  					"fourth-now-first",
   111  					"fifth-now-fourth",
   112  				}, now)
   113  				return &q
   114  			}(),
   115  			fixes: map[string]time.Time{
   116  				"fourth-now-first": now.Add(-2 * time.Minute),
   117  				"first-now-second": now.Add(-time.Minute),
   118  				"second-now-fifth": now.Add(2 * time.Minute),
   119  				"fifth-now-fourth": now.Add(time.Minute),
   120  			},
   121  			later: true,
   122  
   123  			next: []string{
   124  				"fourth-now-first",
   125  				"first-now-second",
   126  				"third",
   127  				"fifth-now-fourth",
   128  				"second-now-fifth",
   129  			},
   130  		},
   131  		{
   132  			name: "reduce",
   133  			q: func() *Queue {
   134  				var q Queue
   135  				q.Init(log, []string{
   136  					"first-now-second",
   137  					"second-ignored-becomes-fifth",
   138  					"third-becomes-fourth",
   139  					"fourth-now-first",
   140  					"fifth-ignored-becomes-fourth",
   141  				}, now)
   142  				return &q
   143  			}(),
   144  			fixes: map[string]time.Time{
   145  				"fourth-now-first":             now.Add(-2 * time.Minute),
   146  				"first-now-second":             now.Add(-time.Minute),
   147  				"second-ignored-becomes-fifth": now.Add(2 * time.Minute), // noop
   148  				"fifth-ignored-becomes-fourth": now.Add(time.Minute),     // noop
   149  			},
   150  			later: true,
   151  
   152  			next: []string{
   153  				"fourth-now-first",
   154  				"first-now-second",
   155  				"third-becomes-fourth",
   156  				"fifth-ignored-becomes-fourth",
   157  				"second-ignored-becomes-fifth",
   158  			},
   159  		},
   160  	}
   161  
   162  	for _, tc := range cases {
   163  		t.Run(tc.name, func(t *testing.T) {
   164  			if err := tc.q.FixAll(tc.fixes, tc.later); (err != nil) != tc.err {
   165  				t.Errorf("FixAll() got unexpected error %v, wanted err=%t", err, tc.err)
   166  			}
   167  			var got []string
   168  			for range tc.next {
   169  				got = append(got, heap.Pop(&tc.q.queue).(*item).name)
   170  			}
   171  			if diff := cmp.Diff(tc.next, got, protocmp.Transform()); diff != "" {
   172  				t.Errorf("FixAll() got unexpected diff (-want +got):\n%s", diff)
   173  			}
   174  		})
   175  	}
   176  }
   177  
   178  func TestFix(t *testing.T) {
   179  	now := time.Now()
   180  	log := logrus.WithField("test", "TestFix")
   181  	cases := []struct {
   182  		name string
   183  
   184  		q     *Queue
   185  		fix   string
   186  		when  time.Time
   187  		later bool
   188  
   189  		next []string
   190  		err  bool
   191  	}{
   192  		{
   193  			name: "missing",
   194  			fix:  "missing",
   195  			q:    &Queue{},
   196  			err:  true,
   197  		},
   198  		{
   199  			name: "later",
   200  			fix:  "basic",
   201  			q: func() *Queue {
   202  				var q Queue
   203  				q.Init(log, []string{
   204  					"basic",
   205  					"was-later-now-first",
   206  				}, now)
   207  				return &q
   208  			}(),
   209  			when:  now.Add(time.Minute),
   210  			later: true,
   211  			next: []string{
   212  				"was-later-now-first",
   213  				"basic",
   214  			},
   215  		},
   216  		{
   217  			name: "ignore later",
   218  			fix:  "basic",
   219  			q: func() *Queue {
   220  				var q Queue
   221  				q.Init(log, []string{
   222  					"basic",
   223  					"was-later-still-later",
   224  				}, now)
   225  				return &q
   226  			}(),
   227  			when: now.Add(time.Minute),
   228  			next: []string{
   229  				"basic",
   230  				"was-later-still-later",
   231  			},
   232  		},
   233  		{
   234  			name: "reduce",
   235  			fix:  "basic",
   236  			q: func() *Queue {
   237  				var q Queue
   238  				q.Init(log, []string{
   239  					"was-earlier-now-later",
   240  					"basic",
   241  				}, now)
   242  				return &q
   243  			}(),
   244  			when: now.Add(-time.Minute),
   245  			next: []string{
   246  				"basic",
   247  				"was-earlier-now-later",
   248  			},
   249  		},
   250  	}
   251  
   252  	for _, tc := range cases {
   253  		t.Run(tc.name, func(t *testing.T) {
   254  			if err := tc.q.Fix(tc.fix, tc.when, tc.later); (err != nil) != tc.err {
   255  				t.Errorf("Fix() got unexpected error %v, wanted err=%t", err, tc.err)
   256  			}
   257  			var got []string
   258  			for range tc.next {
   259  				got = append(got, heap.Pop(&tc.q.queue).(*item).name)
   260  			}
   261  			if diff := cmp.Diff(tc.next, got, protocmp.Transform()); diff != "" {
   262  				t.Errorf("Fix() got unexpected diff (-want +got):\n%s", diff)
   263  			}
   264  		})
   265  	}
   266  }
   267  
   268  func TestStatus(t *testing.T) {
   269  	log := logrus.WithField("test", "TestStatus")
   270  	pstr := func(s string) *string { return &s }
   271  	now := time.Now()
   272  	cases := []struct {
   273  		name string
   274  		q    *Queue
   275  
   276  		depth int
   277  		next  *string
   278  		when  time.Time
   279  	}{
   280  		{
   281  			name: "empty",
   282  			q:    &Queue{},
   283  		},
   284  		{
   285  			name: "single",
   286  			q: func() *Queue {
   287  				var q Queue
   288  				q.Init(log, []string{"hi"}, now)
   289  				return &q
   290  			}(),
   291  			depth: 1,
   292  			next:  pstr("hi"),
   293  			when:  now,
   294  		},
   295  		{
   296  			name: "multi",
   297  			q: func() *Queue {
   298  				var q Queue
   299  				q.Init(log, []string{
   300  					"hi",
   301  					"middle",
   302  					"there",
   303  				}, now)
   304  				q.Fix("middle", now.Add(-time.Minute), true)
   305  				return &q
   306  			}(),
   307  			depth: 3,
   308  			next:  pstr("middle"),
   309  			when:  now.Add(-time.Minute),
   310  		},
   311  	}
   312  
   313  	for _, tc := range cases {
   314  		t.Run(tc.name, func(t *testing.T) {
   315  			depth, next, when := tc.q.Status()
   316  			if want, got := tc.depth, depth; want != got {
   317  				t.Errorf("Status() wanted depth %d, got %d", want, got)
   318  			}
   319  			if diff := cmp.Diff(tc.next, next, protocmp.Transform()); diff != "" {
   320  				t.Errorf("Status() got unexpected next diff (-want +got):\n%s", diff)
   321  			}
   322  			if !when.Equal(tc.when) {
   323  				t.Errorf("Status() wanted when %v, got %v", tc.when, when)
   324  			}
   325  		})
   326  	}
   327  }
   328  
   329  func TestSend(t *testing.T) {
   330  	log := logrus.WithField("test", "TestSend")
   331  	cases := []struct {
   332  		name      string
   333  		q         *Queue
   334  		receivers func(context.Context, *testing.T) (context.Context, chan<- string, func() []string)
   335  		freq      time.Duration
   336  
   337  		want []string
   338  	}{
   339  		{
   340  			name: "empty",
   341  			q:    &Queue{log: log},
   342  			receivers: func(ctx context.Context, t *testing.T) (context.Context, chan<- string, func() []string) {
   343  				ch := make(chan string)
   344  				go func() {
   345  					for {
   346  						select {
   347  						case name := <-ch:
   348  							t.Errorf("Send() receiver got unexpected group: %v", name)
   349  							return
   350  						case <-ctx.Done():
   351  							return
   352  						}
   353  					}
   354  				}()
   355  
   356  				return ctx, ch, func() []string { return nil }
   357  			},
   358  		},
   359  		{
   360  			name: "empty loop",
   361  			q:    &Queue{log: log},
   362  			receivers: func(ctx context.Context, t *testing.T) (context.Context, chan<- string, func() []string) {
   363  				ch := make(chan string)
   364  				ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
   365  				go func() {
   366  					for {
   367  						select {
   368  						case name := <-ch:
   369  							t.Errorf("Send() receiver got unexpected name: %q", name)
   370  							return
   371  						case <-ctx.Done():
   372  							cancel()
   373  							return
   374  						}
   375  					}
   376  				}()
   377  
   378  				return ctx, ch, func() []string { return nil }
   379  			},
   380  			freq: time.Microsecond,
   381  		},
   382  		{
   383  			name: "single",
   384  			q: func() *Queue {
   385  				var q Queue
   386  				q.Init(log, []string{"hi"}, time.Now())
   387  				return &q
   388  			}(),
   389  			receivers: func(ctx context.Context, t *testing.T) (context.Context, chan<- string, func() []string) {
   390  				ch := make(chan string)
   391  				var wg sync.WaitGroup
   392  				wg.Add(1)
   393  				var got []string
   394  				go func() {
   395  					defer wg.Done()
   396  					for {
   397  						select {
   398  						case name := <-ch:
   399  							got = append(got, name)
   400  							return
   401  						case <-ctx.Done():
   402  							return
   403  						}
   404  					}
   405  				}()
   406  
   407  				return ctx, ch, func() []string {
   408  					wg.Wait()
   409  					return got
   410  				}
   411  			},
   412  			want: []string{"hi"},
   413  		},
   414  		{
   415  			name: "single loop",
   416  			q: func() *Queue {
   417  				var q Queue
   418  				q.Init(log, []string{"hi"}, time.Now())
   419  				return &q
   420  			}(),
   421  			receivers: func(ctx context.Context, _ *testing.T) (context.Context, chan<- string, func() []string) {
   422  				ch := make(chan string)
   423  				var wg sync.WaitGroup
   424  				wg.Add(1)
   425  				var got []string
   426  				ctx, cancel := context.WithCancel(ctx)
   427  				go func() {
   428  					defer wg.Done()
   429  					for {
   430  						select {
   431  						case name := <-ch:
   432  							got = append(got, name)
   433  							if len(got) == 3 {
   434  								cancel()
   435  							}
   436  						case <-ctx.Done():
   437  							cancel()
   438  							return
   439  						}
   440  					}
   441  				}()
   442  
   443  				return ctx, ch, func() []string {
   444  					wg.Wait()
   445  					return got
   446  				}
   447  			},
   448  			freq: time.Microsecond,
   449  			want: []string{
   450  				"hi",
   451  				"hi",
   452  				"hi",
   453  			},
   454  		},
   455  		{
   456  			name: "multi",
   457  			q: func() *Queue {
   458  				var q Queue
   459  				q.Init(log, []string{
   460  					"hi",
   461  					"there",
   462  				}, time.Now())
   463  				return &q
   464  			}(),
   465  			receivers: func(ctx context.Context, _ *testing.T) (context.Context, chan<- string, func() []string) {
   466  				ch := make(chan string)
   467  				var wg sync.WaitGroup
   468  				wg.Add(1)
   469  				var got []string
   470  				go func() {
   471  					defer wg.Done()
   472  					for {
   473  						select {
   474  						case name := <-ch:
   475  							got = append(got, name)
   476  							if len(got) == 2 {
   477  								return
   478  							}
   479  						case <-ctx.Done():
   480  							return
   481  						}
   482  					}
   483  				}()
   484  
   485  				return ctx, ch, func() []string {
   486  					wg.Wait()
   487  					return got
   488  				}
   489  			},
   490  			want: []string{
   491  				"hi",
   492  				"there",
   493  			},
   494  		},
   495  		{
   496  			name: "multi loop",
   497  			q: func() *Queue {
   498  				var q Queue
   499  				q.Init(log, []string{"hi", "there"}, time.Now())
   500  				return &q
   501  			}(),
   502  			receivers: func(ctx context.Context, _ *testing.T) (context.Context, chan<- string, func() []string) {
   503  				ch := make(chan string)
   504  				var wg sync.WaitGroup
   505  				wg.Add(1)
   506  				var got []string
   507  				ctx, cancel := context.WithCancel(ctx)
   508  				go func() {
   509  					defer wg.Done()
   510  					for {
   511  						select {
   512  						case name := <-ch:
   513  							got = append(got, name)
   514  							if len(got) == 6 {
   515  								cancel()
   516  							}
   517  						case <-ctx.Done():
   518  							cancel()
   519  							return
   520  						}
   521  					}
   522  				}()
   523  
   524  				return ctx, ch, func() []string {
   525  					wg.Wait()
   526  					return got
   527  				}
   528  			},
   529  			freq: time.Microsecond,
   530  			want: []string{
   531  				"hi",
   532  				"there",
   533  				"hi",
   534  				"there",
   535  				"hi",
   536  				"there",
   537  			},
   538  		},
   539  	}
   540  
   541  	for _, tc := range cases {
   542  		t.Run(tc.name, func(t *testing.T) {
   543  
   544  			ctx, cancel := context.WithCancel(context.Background())
   545  			defer cancel()
   546  
   547  			ctx, channel, get := tc.receivers(ctx, t)
   548  			if err := tc.q.Send(ctx, channel, tc.freq); err != ctx.Err() {
   549  				t.Errorf("Send() returned unexpected error: want %v, got %v", ctx.Err(), err)
   550  			}
   551  			got := get()
   552  			if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" {
   553  				t.Errorf("Send() got unexpected diff (-want +got):\n%s", diff)
   554  			}
   555  		})
   556  	}
   557  }
   558  
   559  func TestPriorityQueue(t *testing.T) {
   560  	cases := []struct {
   561  		name  string
   562  		items []*item
   563  		want  []string
   564  	}{
   565  		{
   566  			name: "basic",
   567  		},
   568  		{
   569  			name: "single",
   570  			items: []*item{
   571  				{
   572  					name: "hi",
   573  				},
   574  			},
   575  			want: []string{"hi"},
   576  		},
   577  		{
   578  			name: "desc",
   579  			items: []*item{
   580  				{
   581  					name: "young",
   582  					when: time.Now(),
   583  				},
   584  				{
   585  					name: "old",
   586  					when: time.Now().Add(-time.Hour),
   587  				},
   588  			},
   589  			want: []string{"old", "young"},
   590  		},
   591  		{
   592  			name: "asc",
   593  			items: []*item{
   594  				{
   595  					name: "old",
   596  					when: time.Now().Add(-time.Hour),
   597  				},
   598  				{
   599  					name: "young",
   600  					when: time.Now(),
   601  				},
   602  			},
   603  			want: []string{"old", "young"},
   604  		},
   605  	}
   606  
   607  	for _, tc := range cases {
   608  		t.Run(tc.name, func(t *testing.T) {
   609  			pq := priorityQueue(tc.items)
   610  			heap.Init(&pq)
   611  			var got []string
   612  			for i, w := range tc.want {
   613  				g := pq.peek().name
   614  				if diff := cmp.Diff(w, g, protocmp.Transform()); diff != "" {
   615  					t.Errorf("%d peek() got unexpected diff (-want +got):\n%s", i, diff)
   616  				}
   617  				got = append(got, heap.Pop(&pq).(*item).name)
   618  			}
   619  			if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" {
   620  				t.Errorf("priorityQueue() got unexpected diff (-want +got):\n%s", diff)
   621  			}
   622  		})
   623  	}
   624  }
   625  
   626  func TestSleep(t *testing.T) {
   627  	cases := []struct {
   628  		name       string
   629  		sleep      time.Duration
   630  		rouseAfter time.Duration // if specified, rouse after this time
   631  		wantRouse  bool
   632  	}{
   633  		{
   634  			name:      "basic",
   635  			sleep:     100 * time.Millisecond,
   636  			wantRouse: false,
   637  		},
   638  		{
   639  			name:       "rouse during sleep",
   640  			sleep:      1 * time.Minute,
   641  			rouseAfter: 100 * time.Millisecond,
   642  			wantRouse:  true,
   643  		},
   644  		{
   645  			name:       "rouse after sleep",
   646  			sleep:      100 * time.Millisecond,
   647  			rouseAfter: 1 * time.Second,
   648  			wantRouse:  false,
   649  		},
   650  	}
   651  	for _, tc := range cases {
   652  		t.Run(tc.name, func(t *testing.T) {
   653  			var q Queue
   654  			q.Init(logrus.WithField("name", tc.name), []string{"hi", "there"}, time.Now())
   655  
   656  			var slept, roused bool
   657  			var lock sync.Mutex
   658  			go func(rouseAfter time.Duration) {
   659  				if rouseAfter == 0 {
   660  					return
   661  				}
   662  				time.Sleep(rouseAfter)
   663  				q.rouse()
   664  				lock.Lock()
   665  				if !slept {
   666  					roused = true
   667  				}
   668  				lock.Unlock()
   669  			}(tc.rouseAfter)
   670  
   671  			q.sleep(tc.sleep)
   672  			lock.Lock()
   673  			slept = true
   674  			lock.Unlock()
   675  
   676  			if tc.wantRouse != roused {
   677  				t.Errorf("sleep() roused incorrectly (with sleep %q and rouse after %q): want rouse = %t, got %t", tc.sleep, tc.rouseAfter, tc.wantRouse, roused)
   678  			}
   679  		})
   680  	}
   681  }