github.com/GoogleCloudPlatform/testgrid@v0.0.174/cmd/state_comparer/main_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  // A script to quickly check two TestGrid state.protos do not wildly differ.
    18  // Assume that if the column and row names are approx. equivalent, the state
    19  // is probably reasonable.
    20  
    21  package main
    22  
    23  import (
    24  	"context"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/GoogleCloudPlatform/testgrid/util/gcs"
    29  
    30  	statepb "github.com/GoogleCloudPlatform/testgrid/pb/state"
    31  )
    32  
    33  func newPathOrDie(s string) gcs.Path {
    34  	p, err := gcs.NewPath(s)
    35  	if err != nil {
    36  		panic(err)
    37  	}
    38  	return *p
    39  }
    40  
    41  func TestValidate(t *testing.T) {
    42  	cases := []struct {
    43  		name        string
    44  		first       gcs.Path
    45  		second      gcs.Path
    46  		diffRatioOK float64
    47  		err         bool
    48  	}{
    49  		{
    50  			name:   "empty paths, error",
    51  			first:  newPathOrDie(""),
    52  			second: newPathOrDie(""),
    53  			err:    true,
    54  		},
    55  		{
    56  			name:   "empty first path, error",
    57  			first:  newPathOrDie(""),
    58  			second: newPathOrDie("gs://path/to/second"),
    59  			err:    true,
    60  		},
    61  		{
    62  			name:   "empty second path, error",
    63  			first:  newPathOrDie("gs://path/to/first"),
    64  			second: newPathOrDie(""),
    65  			err:    true,
    66  		},
    67  		{
    68  			name:   "paths basically work",
    69  			first:  newPathOrDie("gs://path/to/first"),
    70  			second: newPathOrDie("gs://path/to/second"),
    71  		},
    72  		{
    73  			name:        "reject negative ratio",
    74  			first:       newPathOrDie("gs://path/to/first"),
    75  			second:      newPathOrDie("gs://path/to/second"),
    76  			diffRatioOK: -0.5,
    77  			err:         true,
    78  		},
    79  		{
    80  			name:        "reject ratio over 1.0",
    81  			first:       newPathOrDie("gs://path/to/first"),
    82  			second:      newPathOrDie("gs://path/to/second"),
    83  			diffRatioOK: 1.5,
    84  			err:         true,
    85  		},
    86  		{
    87  			name:        "ratio basically works",
    88  			first:       newPathOrDie("gs://path/to/first"),
    89  			second:      newPathOrDie("gs://path/to/second"),
    90  			diffRatioOK: 0.5,
    91  		},
    92  	}
    93  
    94  	for _, tc := range cases {
    95  		t.Run(tc.name, func(t *testing.T) {
    96  			opt := options{
    97  				first:       tc.first,
    98  				second:      tc.second,
    99  				diffRatioOK: tc.diffRatioOK,
   100  			}
   101  			err := opt.validate()
   102  			if tc.err && err == nil {
   103  				t.Fatalf("validate() (%v), expected error but got none", opt)
   104  			}
   105  			if !tc.err {
   106  				if err != nil {
   107  					t.Fatalf("validate() (%v) got unexpected error %v", opt, err)
   108  				}
   109  				// Also check that paths end in a '/'.
   110  				if !strings.HasSuffix(opt.first.String(), "/") {
   111  					t.Errorf("first path %q should end in '/'.", tc.first.String())
   112  				}
   113  				if !strings.HasSuffix(opt.second.String(), "/") {
   114  					t.Errorf("second path %q should end in '/'.", tc.second.String())
   115  				}
   116  			}
   117  		})
   118  	}
   119  }
   120  
   121  func TestCompare(t *testing.T) {
   122  	cases := []struct {
   123  		name        string
   124  		first       *statepb.Grid
   125  		second      *statepb.Grid
   126  		diffRatioOK float64
   127  		diffed      bool
   128  	}{
   129  		{
   130  			name:   "nil grids, same",
   131  			diffed: false,
   132  		},
   133  		{
   134  			name:   "empty grids, same",
   135  			first:  &statepb.Grid{},
   136  			second: &statepb.Grid{},
   137  			diffed: false,
   138  		},
   139  		{
   140  			name: "basic grids, same",
   141  			first: &statepb.Grid{
   142  				Rows: []*statepb.Row{
   143  					{Name: "row1"},
   144  					{Name: "row2"},
   145  				},
   146  				Columns: []*statepb.Column{
   147  					{Build: "col1"},
   148  					{Build: "col2"},
   149  				},
   150  			},
   151  			second: &statepb.Grid{
   152  				Rows: []*statepb.Row{
   153  					{Name: "row1"},
   154  					{Name: "row2"},
   155  				},
   156  				Columns: []*statepb.Column{
   157  					{Build: "col1"},
   158  					{Build: "col2"},
   159  				},
   160  			},
   161  			diffed: false,
   162  		},
   163  		{
   164  			name: "different rows, diff",
   165  			first: &statepb.Grid{
   166  				Rows: []*statepb.Row{
   167  					{Name: "row1"},
   168  				},
   169  				Columns: []*statepb.Column{
   170  					{Build: "col1"},
   171  					{Build: "col2"},
   172  				},
   173  			},
   174  			second: &statepb.Grid{
   175  				Rows: []*statepb.Row{
   176  					{Name: "row2"},
   177  				},
   178  				Columns: []*statepb.Column{
   179  					{Build: "col1"},
   180  					{Build: "col2"},
   181  				},
   182  			},
   183  			diffed: true,
   184  		},
   185  		{
   186  			name: "different rows lower than ratio, same",
   187  			first: &statepb.Grid{
   188  				Rows: []*statepb.Row{
   189  					{Name: "row1"},
   190  					{Name: "row2"},
   191  				},
   192  				Columns: []*statepb.Column{
   193  					{Build: "col1"},
   194  					{Build: "col2"},
   195  				},
   196  			},
   197  			second: &statepb.Grid{
   198  				Rows: []*statepb.Row{
   199  					{Name: "row1"},
   200  				},
   201  				Columns: []*statepb.Column{
   202  					{Build: "col1"},
   203  					{Build: "col2"},
   204  				},
   205  			},
   206  			diffRatioOK: 0.6,
   207  		},
   208  		{
   209  			name: "different columns, diff",
   210  			first: &statepb.Grid{
   211  				Rows: []*statepb.Row{
   212  					{Name: "row1"},
   213  					{Name: "row2"},
   214  				},
   215  				Columns: []*statepb.Column{
   216  					{Build: "col1"},
   217  					{Build: "col2"},
   218  				},
   219  			},
   220  			second: &statepb.Grid{
   221  				Rows: []*statepb.Row{
   222  					{Name: "row1"},
   223  					{Name: "row2"},
   224  				},
   225  				Columns: []*statepb.Column{
   226  					{Build: "col3"},
   227  					{Build: "col4"},
   228  				},
   229  			},
   230  			diffed: true,
   231  		},
   232  		{
   233  			name: "different columns lower than ratio, same",
   234  			first: &statepb.Grid{
   235  				Rows: []*statepb.Row{
   236  					{Name: "row1"},
   237  					{Name: "row2"},
   238  				},
   239  				Columns: []*statepb.Column{
   240  					{Build: "col1"},
   241  					{Build: "col2"},
   242  				},
   243  			},
   244  			second: &statepb.Grid{
   245  				Rows: []*statepb.Row{
   246  					{Name: "row1"},
   247  					{Name: "row2"},
   248  				},
   249  				Columns: []*statepb.Column{
   250  					{Build: "col1"},
   251  				},
   252  			},
   253  			diffRatioOK: 0.6,
   254  			diffed:      false,
   255  		},
   256  		{
   257  			name: "different grids, diff",
   258  			first: &statepb.Grid{
   259  				Rows: []*statepb.Row{
   260  					{Name: "row1"},
   261  					{Name: "row2"},
   262  				},
   263  				Columns: []*statepb.Column{
   264  					{Build: "col1"},
   265  					{Build: "col2"},
   266  				},
   267  			},
   268  			second: &statepb.Grid{
   269  				Rows: []*statepb.Row{
   270  					{Name: "row1"},
   271  				},
   272  				Columns: []*statepb.Column{
   273  					{Build: "col1"},
   274  				},
   275  			},
   276  			diffed: true,
   277  		},
   278  		{
   279  			name: "different grids lower than ratio, same",
   280  			first: &statepb.Grid{
   281  				Rows: []*statepb.Row{
   282  					{Name: "row1"},
   283  					{Name: "row2"},
   284  				},
   285  				Columns: []*statepb.Column{
   286  					{Build: "col1"},
   287  					{Build: "col2"},
   288  				},
   289  			},
   290  			second: &statepb.Grid{
   291  				Rows: []*statepb.Row{
   292  					{Name: "row1"},
   293  				},
   294  				Columns: []*statepb.Column{
   295  					{Build: "col1"},
   296  				},
   297  			},
   298  			diffRatioOK: 0.6,
   299  			diffed:      false,
   300  		},
   301  		{
   302  			name: "diff cols but empty rows, same",
   303  			first: &statepb.Grid{
   304  				Rows: []*statepb.Row{},
   305  				Columns: []*statepb.Column{
   306  					{Build: "col1"},
   307  				},
   308  			},
   309  			second: &statepb.Grid{
   310  				Rows: []*statepb.Row{},
   311  				Columns: []*statepb.Column{
   312  					{Build: "col1"},
   313  					{Build: "col2"},
   314  				},
   315  			},
   316  			diffed: false,
   317  		},
   318  		{
   319  			name: "diff rows but empty cols, same",
   320  			first: &statepb.Grid{
   321  				Rows: []*statepb.Row{
   322  					{Name: "row1"},
   323  					{Name: "row2"},
   324  				},
   325  				Columns: []*statepb.Column{},
   326  			},
   327  			second: &statepb.Grid{
   328  				Rows: []*statepb.Row{
   329  					{Name: "row1"},
   330  				},
   331  				Columns: []*statepb.Column{},
   332  			},
   333  			diffed: false,
   334  		},
   335  		{
   336  			name: "first empty, second has one column, same",
   337  			first: &statepb.Grid{
   338  				Rows:    []*statepb.Row{},
   339  				Columns: []*statepb.Column{},
   340  			},
   341  			second: &statepb.Grid{
   342  				Rows: []*statepb.Row{
   343  					{Name: "row1"},
   344  					{Name: "row2"},
   345  					{Name: "row3"},
   346  				},
   347  				Columns: []*statepb.Column{
   348  					{Build: "col1"},
   349  				},
   350  			},
   351  			diffed: false,
   352  		},
   353  	}
   354  
   355  	for _, tc := range cases {
   356  		t.Run(tc.name, func(t *testing.T) {
   357  			ctx := context.Background()
   358  			if diffed, _, _ := compare(ctx, tc.first, tc.second, tc.diffRatioOK, 5); diffed != tc.diffed {
   359  				t.Errorf("compare(%s, %s) not as expected; got %t, want %t", tc.first, tc.second, diffed, tc.diffed)
   360  			}
   361  		})
   362  	}
   363  }
   364  
   365  func TestReportDiff(t *testing.T) {
   366  	cases := []struct {
   367  		name        string
   368  		first       map[string]int
   369  		second      map[string]int
   370  		diffRatioOK float64
   371  		diffed      bool
   372  		reasons     diffReasons
   373  	}{
   374  		{
   375  			name:   "nil",
   376  			diffed: false,
   377  		},
   378  		{
   379  			name: "same",
   380  			first: map[string]int{
   381  				"some": 1,
   382  				"more": 2,
   383  			},
   384  			second: map[string]int{
   385  				"some": 1,
   386  				"more": 2,
   387  			},
   388  			diffed: false,
   389  		},
   390  		{
   391  			name: "different",
   392  			first: map[string]int{
   393  				"some": 1,
   394  				"more": 1,
   395  			},
   396  			second: map[string]int{
   397  				"hi":    1,
   398  				"hello": 1,
   399  				"yay":   1,
   400  			},
   401  			diffed:  true,
   402  			reasons: diffReasons{other: true},
   403  		},
   404  		{
   405  			name: "different above ratio",
   406  			first: map[string]int{
   407  				"some": 1,
   408  				"more": 1,
   409  			},
   410  			second: map[string]int{
   411  				"some": 1,
   412  				"more": 1,
   413  				"hi":   1,
   414  			},
   415  			diffRatioOK: 0.3,
   416  			diffed:      true,
   417  			reasons:     diffReasons{other: true},
   418  		},
   419  		{
   420  			name: "different below ratio",
   421  			first: map[string]int{
   422  				"some": 1,
   423  				"more": 1,
   424  			},
   425  			second: map[string]int{
   426  				"some": 1,
   427  				"more": 1,
   428  				"hi":   1,
   429  			},
   430  			diffRatioOK: 0.6,
   431  			diffed:      false,
   432  			reasons:     diffReasons{},
   433  		},
   434  		{
   435  			name: "first has duplicates",
   436  			first: map[string]int{
   437  				"some": 3,
   438  				"more": 5,
   439  			},
   440  			second: map[string]int{
   441  				"some": 1,
   442  				"more": 1,
   443  			},
   444  			diffed:  true,
   445  			reasons: diffReasons{firstHasDuplicates: true},
   446  		},
   447  		{
   448  			name: "second has duplicates",
   449  			first: map[string]int{
   450  				"some": 1,
   451  				"more": 1,
   452  			},
   453  			second: map[string]int{
   454  				"some": 3,
   455  				"more": 5,
   456  			},
   457  			diffed:  true,
   458  			reasons: diffReasons{secondHasDuplicates: true},
   459  		},
   460  		{
   461  			name: "first has duplicates below ratio",
   462  			first: map[string]int{
   463  				"some": 1,
   464  				"more": 2,
   465  			},
   466  			second: map[string]int{
   467  				"some": 1,
   468  				"more": 1,
   469  			},
   470  			diffRatioOK: 0.6,
   471  			diffed:      false,
   472  			reasons:     diffReasons{},
   473  		},
   474  		{
   475  			name: "second has duplicates below ratio",
   476  			first: map[string]int{
   477  				"some": 1,
   478  				"more": 1,
   479  			},
   480  			second: map[string]int{
   481  				"some": 1,
   482  				"more": 2,
   483  			},
   484  			diffRatioOK: 0.6,
   485  			diffed:      false,
   486  			reasons:     diffReasons{},
   487  		},
   488  		{
   489  			name: "first has duplicates above ratio",
   490  			first: map[string]int{
   491  				"some": 2,
   492  				"more": 2,
   493  			},
   494  			second: map[string]int{
   495  				"some": 1,
   496  				"more": 1,
   497  			},
   498  			diffRatioOK: 0.3,
   499  			diffed:      true,
   500  			reasons:     diffReasons{firstHasDuplicates: true},
   501  		},
   502  		{
   503  			name: "second has duplicates above ratio",
   504  			first: map[string]int{
   505  				"some": 1,
   506  				"more": 1,
   507  			},
   508  			second: map[string]int{
   509  				"some": 2,
   510  				"more": 2,
   511  			},
   512  			diffRatioOK: 0.3,
   513  			diffed:      true,
   514  			reasons:     diffReasons{secondHasDuplicates: true},
   515  		},
   516  		{
   517  			name: "second has duplicates and differences above ratio",
   518  			first: map[string]int{
   519  				"some": 1,
   520  				"more": 1,
   521  			},
   522  			second: map[string]int{
   523  				"some": 2,
   524  				"more": 2,
   525  				"hi":   5,
   526  			},
   527  			diffRatioOK: 0.3,
   528  			diffed:      true,
   529  			reasons:     diffReasons{other: true},
   530  		},
   531  	}
   532  
   533  	for _, tc := range cases {
   534  		t.Run(tc.name, func(t *testing.T) {
   535  			diffed, reasons := reportDiff(tc.first, tc.second, "thing", tc.diffRatioOK)
   536  			if diffed != tc.diffed {
   537  				t.Errorf("reportDiff(%v, %v, %f) diffed wrong; got %t, want %t", tc.first, tc.second, tc.diffRatioOK, diffed, tc.diffed)
   538  			}
   539  			if reasons != tc.reasons {
   540  				t.Errorf("reportDiff(%v, %v, %f) has wrong diff reasons; got %t, want %t", tc.first, tc.second, tc.diffRatioOK, reasons, tc.reasons)
   541  			}
   542  		})
   543  	}
   544  }
   545  
   546  func TestCompareKeys(t *testing.T) {
   547  	cases := []struct {
   548  		name        string
   549  		first       map[string]int
   550  		second      map[string]int
   551  		diffRatioOK float64
   552  		diffed      bool
   553  	}{
   554  		{
   555  			name:   "nil",
   556  			diffed: false,
   557  		},
   558  		{
   559  			name:   "empty",
   560  			first:  map[string]int{},
   561  			second: map[string]int{},
   562  			diffed: false,
   563  		},
   564  		{
   565  			name: "maps match",
   566  			first: map[string]int{
   567  				"some": 1,
   568  				"more": 2,
   569  			},
   570  			second: map[string]int{
   571  				"some": 1,
   572  				"more": 2,
   573  			},
   574  			diffed: false,
   575  		},
   576  		{
   577  			name: "non-matching keys",
   578  			first: map[string]int{
   579  				"some": 5,
   580  				"more": 3,
   581  			},
   582  			second: map[string]int{
   583  				"hi":    1,
   584  				"hello": 1,
   585  				"other": 1,
   586  			},
   587  			diffed: true,
   588  		},
   589  		{
   590  			name: "duplicate keys",
   591  			first: map[string]int{
   592  				"some": 5,
   593  				"more": 3,
   594  			},
   595  			second: map[string]int{
   596  				"some": 1,
   597  				"more": 1,
   598  			},
   599  			diffed: false,
   600  		},
   601  		{
   602  			name: "mostly duplicate keys below ratio",
   603  			first: map[string]int{
   604  				"some": 5,
   605  				"more": 3,
   606  			},
   607  			second: map[string]int{
   608  				"some": 1,
   609  				"more": 1,
   610  				"hi":   1,
   611  			},
   612  			diffRatioOK: 0.6,
   613  			diffed:      false,
   614  		},
   615  		{
   616  			name: "mostly duplicate keys above ratio",
   617  			first: map[string]int{
   618  				"some": 5,
   619  				"more": 3,
   620  			},
   621  			second: map[string]int{
   622  				"some": 1,
   623  				"more": 1,
   624  				"hi":   1,
   625  			},
   626  			diffRatioOK: 0.3,
   627  			diffed:      true,
   628  		},
   629  	}
   630  	for _, tc := range cases {
   631  		t.Run(tc.name, func(t *testing.T) {
   632  			if diffed := compareKeys(tc.first, tc.second, tc.diffRatioOK); diffed != tc.diffed {
   633  				t.Errorf("compareKeys(%v, %v, %f) not as expected; got %t, want %t", tc.first, tc.second, tc.diffRatioOK, diffed, tc.diffed)
   634  			}
   635  		})
   636  	}
   637  }