github.com/GoogleCloudPlatform/testgrid@v0.0.174/pkg/api/v1/state_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 v1
    18  
    19  import (
    20  	"context"
    21  	"reflect"
    22  	"testing"
    23  
    24  	apipb "github.com/GoogleCloudPlatform/testgrid/pb/api/v1"
    25  
    26  	pb "github.com/GoogleCloudPlatform/testgrid/pb/config"
    27  
    28  	statepb "github.com/GoogleCloudPlatform/testgrid/pb/state"
    29  	"github.com/google/go-cmp/cmp"
    30  	"google.golang.org/protobuf/testing/protocmp"
    31  	"google.golang.org/protobuf/types/known/timestamppb"
    32  )
    33  
    34  func TestDecodeRLE(t *testing.T) {
    35  	tests := []struct {
    36  		name        string
    37  		encodedData []int32
    38  		expected    []int32
    39  	}{
    40  		{
    41  			name:        "returns empty result if empty encoded data",
    42  			encodedData: []int32{},
    43  			expected:    []int32{},
    44  		},
    45  		{
    46  			name:        "returns empty result if not valid encoded data",
    47  			encodedData: []int32{1, 3, 4},
    48  			expected:    []int32{},
    49  		},
    50  		{
    51  			name:        "returns correct decoded result if valid encoded data",
    52  			encodedData: []int32{1, 2, 3, 4},
    53  			expected:    []int32{1, 1, 3, 3, 3, 3},
    54  		},
    55  		{
    56  			name:     "returns empty result if null encoded data",
    57  			expected: []int32{},
    58  		},
    59  	}
    60  
    61  	for _, test := range tests {
    62  		t.Run(test.name, func(t *testing.T) {
    63  			result := decodeRLE(test.encodedData)
    64  			if (test.expected == nil && result != nil) || len(result) != len(test.expected) || (len(test.expected) != 0 && !reflect.DeepEqual(result, test.expected)) {
    65  				t.Errorf("Want %q, but got %q", test.expected, result)
    66  			}
    67  		})
    68  	}
    69  }
    70  
    71  func TestListHeaders(t *testing.T) {
    72  	tests := []struct {
    73  		name        string
    74  		config      map[string]*pb.Configuration
    75  		grid        map[string]*statepb.Grid
    76  		req         *apipb.ListHeadersRequest
    77  		want        *apipb.ListHeadersResponse
    78  		expectError bool
    79  	}{
    80  		{
    81  			name: "Returns an error when there's no dashboard resource",
    82  			config: map[string]*pb.Configuration{
    83  				"gs://default/config": {},
    84  			},
    85  			req: &apipb.ListHeadersRequest{
    86  				Dashboard: "missing",
    87  				Tab:       "some tab",
    88  			},
    89  			expectError: true,
    90  		},
    91  		{
    92  			name: "Returns an error when there's no tab resource",
    93  			config: map[string]*pb.Configuration{
    94  				"gs://default/config": {
    95  					Dashboards: []*pb.Dashboard{
    96  						{
    97  							Name:         "Dashboard1",
    98  							DashboardTab: []*pb.DashboardTab{},
    99  						},
   100  					},
   101  				},
   102  			},
   103  			req: &apipb.ListHeadersRequest{
   104  				Dashboard: "dashboard1",
   105  				Tab:       "missing",
   106  			},
   107  			expectError: true,
   108  		},
   109  		{
   110  			name: "Returns empty headers list from a tab",
   111  			config: map[string]*pb.Configuration{
   112  				"gs://default/config": {
   113  					Dashboards: []*pb.Dashboard{
   114  						{
   115  							Name: "Dashboard1",
   116  							DashboardTab: []*pb.DashboardTab{
   117  								{
   118  									Name:          "tab 1",
   119  									TestGroupName: "testgroupname",
   120  								},
   121  							},
   122  						},
   123  					},
   124  				},
   125  			},
   126  			grid: map[string]*statepb.Grid{
   127  				"gs://default/tabs/Dashboard1/tab%201": {},
   128  			},
   129  			req: &apipb.ListHeadersRequest{
   130  				Dashboard: "Dashboard1",
   131  				Tab:       "tab 1",
   132  			},
   133  			want: &apipb.ListHeadersResponse{},
   134  		},
   135  		{
   136  			name: "Returns correct headers from a tab",
   137  			config: map[string]*pb.Configuration{
   138  				"gs://default/config": {
   139  					Dashboards: []*pb.Dashboard{
   140  						{
   141  							Name: "Dashboard1",
   142  							DashboardTab: []*pb.DashboardTab{
   143  								{
   144  									Name:          "tab 1",
   145  									TestGroupName: "testgroupname",
   146  								},
   147  							},
   148  						},
   149  					},
   150  				},
   151  			},
   152  			grid: map[string]*statepb.Grid{
   153  				"gs://default/tabs/Dashboard1/tab%201": {
   154  					Columns: []*statepb.Column{
   155  						{
   156  							Build:   "99",
   157  							Hint:    "99",
   158  							Started: 1635693255000, // Milliseconds
   159  							Extra:   []string{""},
   160  						},
   161  						{
   162  							Build:   "80",
   163  							Hint:    "80",
   164  							Started: 1635779655000, // Milliseconds
   165  							Extra:   []string{"build80"},
   166  						},
   167  					},
   168  				},
   169  			},
   170  			req: &apipb.ListHeadersRequest{
   171  				Dashboard: "dashboard1",
   172  				Tab:       "tab1",
   173  			},
   174  			want: &apipb.ListHeadersResponse{
   175  				Headers: []*apipb.ListHeadersResponse_Header{
   176  					{
   177  						Build:   "99",
   178  						Started: &timestamppb.Timestamp{Seconds: 1635693255},
   179  						Extra:   []string{""},
   180  					},
   181  					{
   182  						Build:   "80",
   183  						Started: &timestamppb.Timestamp{Seconds: 1635779655},
   184  						Extra:   []string{"build80"},
   185  					},
   186  				},
   187  			},
   188  		},
   189  		{
   190  			name: "Returns correct timestamps from a tab",
   191  			config: map[string]*pb.Configuration{
   192  				"gs://default/config": {
   193  					Dashboards: []*pb.Dashboard{
   194  						{
   195  							Name: "Dashboard1",
   196  							DashboardTab: []*pb.DashboardTab{
   197  								{
   198  									Name:          "tab 1",
   199  									TestGroupName: "testgroupname",
   200  								},
   201  							},
   202  						},
   203  					},
   204  				},
   205  			},
   206  			grid: map[string]*statepb.Grid{
   207  				"gs://default/tabs/Dashboard1/tab%201": {
   208  					Columns: []*statepb.Column{
   209  						{
   210  							Build:   "99",
   211  							Hint:    "99",
   212  							Started: 1, // Milliseconds
   213  						},
   214  						{
   215  							Build:   "80",
   216  							Hint:    "80",
   217  							Started: 1635779655123, // Milliseconds
   218  						},
   219  					},
   220  				},
   221  			},
   222  			req: &apipb.ListHeadersRequest{
   223  				Dashboard: "dashboard 1",
   224  				Tab:       "tab 1",
   225  			},
   226  			want: &apipb.ListHeadersResponse{
   227  				Headers: []*apipb.ListHeadersResponse_Header{
   228  					{
   229  						Build:   "99",
   230  						Started: &timestamppb.Timestamp{Nanos: 1000000},
   231  					},
   232  					{
   233  						Build:   "80",
   234  						Started: &timestamppb.Timestamp{Seconds: 1635779655, Nanos: 123000000},
   235  					},
   236  				},
   237  			},
   238  		},
   239  		{
   240  			name: "Server error with unreadable config",
   241  			config: map[string]*pb.Configuration{
   242  				"gs://default/config": {},
   243  			},
   244  			req: &apipb.ListHeadersRequest{
   245  				Dashboard: "dashboard 1",
   246  				Tab:       "tab 1",
   247  			},
   248  			expectError: true,
   249  		},
   250  	}
   251  
   252  	for _, tc := range tests {
   253  		t.Run(tc.name, func(t *testing.T) {
   254  			server := setupTestServer(t, tc.config, tc.grid, nil)
   255  			got, err := server.ListHeaders(context.Background(), tc.req)
   256  			switch {
   257  			case err != nil:
   258  				if !tc.expectError {
   259  					t.Errorf("got unexpected error: %v", err)
   260  				}
   261  			case tc.expectError:
   262  				t.Error("failed to receive an error")
   263  			default:
   264  				if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" {
   265  					t.Errorf("got unexpected diff (-want +got):\n%s", diff)
   266  				}
   267  			}
   268  		})
   269  
   270  	}
   271  
   272  }
   273  
   274  func TestListRows(t *testing.T) {
   275  	tests := []struct {
   276  		name   string
   277  		config map[string]*pb.Configuration
   278  		grid   map[string]*statepb.Grid
   279  		patch  func(*Server)
   280  		req    *apipb.ListRowsRequest
   281  		want   *apipb.ListRowsResponse
   282  		err    bool
   283  	}{
   284  		{
   285  			name: "Returns an error when there's no dashboard resource",
   286  			config: map[string]*pb.Configuration{
   287  				"gs://default/config": {},
   288  			},
   289  			req: &apipb.ListRowsRequest{
   290  				Scope:     "gs://default",
   291  				Dashboard: "missing",
   292  				Tab:       "irrelevant",
   293  			},
   294  			err: true,
   295  		},
   296  		{
   297  			name: "Returns an error when there's no tab resource",
   298  			config: map[string]*pb.Configuration{
   299  				"gs://default/config": {
   300  					Dashboards: []*pb.Dashboard{
   301  						{
   302  							Name:         "Dashboard1",
   303  							DashboardTab: []*pb.DashboardTab{},
   304  						},
   305  					},
   306  				},
   307  			},
   308  			req: &apipb.ListRowsRequest{
   309  				Scope:     "gs://default",
   310  				Dashboard: "Dashboard1",
   311  				Tab:       "irrelevant",
   312  			},
   313  			err: true,
   314  		},
   315  		{
   316  			name: "Returns empty rows list from a tab",
   317  			config: map[string]*pb.Configuration{
   318  				"gs://default/config": {
   319  					Dashboards: []*pb.Dashboard{
   320  						{
   321  							Name: "Dashboard1",
   322  							DashboardTab: []*pb.DashboardTab{
   323  								{
   324  									Name:          "tab 1",
   325  									TestGroupName: "testgroupname",
   326  								},
   327  							},
   328  						},
   329  					},
   330  				},
   331  			},
   332  			grid: map[string]*statepb.Grid{
   333  				"gs://default/tabs/Dashboard1/tab%201": {},
   334  			},
   335  			req: &apipb.ListRowsRequest{
   336  				Scope:     "gs://default",
   337  				Dashboard: "dashboard1",
   338  				Tab:       "tab1",
   339  			},
   340  			want: &apipb.ListRowsResponse{},
   341  		},
   342  		{
   343  			name: "Returns correct rows from a tab",
   344  			config: map[string]*pb.Configuration{
   345  				"gs://default/config": {
   346  					Dashboards: []*pb.Dashboard{
   347  						{
   348  							Name: "Dashboard1",
   349  							DashboardTab: []*pb.DashboardTab{
   350  								{
   351  									Name:          "tab 1",
   352  									TestGroupName: "testgroupname",
   353  								},
   354  							},
   355  						},
   356  					},
   357  				},
   358  			},
   359  			grid: map[string]*statepb.Grid{
   360  				"gs://default/tabs/Dashboard1/tab%201": {
   361  					Rows: []*statepb.Row{
   362  						{
   363  							Name:     "tabrow1",
   364  							Id:       "tabrow1",
   365  							Results:  []int32{1, 2},
   366  							CellIds:  []string{"cell-1", "cell-2"},
   367  							Messages: []string{"", "", "", ""},
   368  							Icons:    []string{"", "", "", ""},
   369  						},
   370  					},
   371  				},
   372  			},
   373  			req: &apipb.ListRowsRequest{
   374  				Scope:     "gs://default",
   375  				Dashboard: "dashboard1",
   376  				Tab:       "tab1",
   377  			},
   378  			want: &apipb.ListRowsResponse{
   379  				Rows: []*apipb.ListRowsResponse_Row{
   380  					{
   381  						Name: "tabrow1",
   382  						Cells: []*apipb.ListRowsResponse_Cell{
   383  							{
   384  								Result: 1,
   385  								CellId: "cell-1",
   386  							},
   387  							{
   388  								Result: 1,
   389  								CellId: "cell-2",
   390  							},
   391  						},
   392  					},
   393  				},
   394  			},
   395  		},
   396  		{
   397  			name: "Returns correct rows with blank cells",
   398  			config: map[string]*pb.Configuration{
   399  				"gs://default/config": {
   400  					Dashboards: []*pb.Dashboard{
   401  						{
   402  							Name: "Dashboard1",
   403  							DashboardTab: []*pb.DashboardTab{
   404  								{
   405  									Name:          "tab 1",
   406  									TestGroupName: "testgroupname",
   407  								},
   408  							},
   409  						},
   410  					},
   411  				},
   412  			},
   413  			grid: map[string]*statepb.Grid{
   414  				"gs://default/tabs/Dashboard1/tab%201": {
   415  					Rows: []*statepb.Row{
   416  						{
   417  							Name:     "tabrow1",
   418  							Id:       "tabrow1",
   419  							Results:  []int32{1, 2, 0, 2, 1, 1, 0, 3, 1, 1},
   420  							CellIds:  []string{"cell-1", "cell-2", "cell-5", "cell-9"},
   421  							Messages: []string{"", "", "hello", "no"},
   422  							Icons:    []string{"", "", "H", "N"},
   423  						},
   424  					},
   425  				},
   426  			},
   427  			req: &apipb.ListRowsRequest{
   428  				Scope:     "gs://default",
   429  				Dashboard: "dashboard1",
   430  				Tab:       "tab1",
   431  			},
   432  			want: &apipb.ListRowsResponse{
   433  				Rows: []*apipb.ListRowsResponse_Row{
   434  					{
   435  						Name: "tabrow1",
   436  						Cells: []*apipb.ListRowsResponse_Cell{
   437  							{
   438  								Result: 1,
   439  								CellId: "cell-1",
   440  							},
   441  							{
   442  								Result: 1,
   443  								CellId: "cell-2",
   444  							},
   445  							{},
   446  							{},
   447  							{
   448  								Result:  1,
   449  								CellId:  "cell-5",
   450  								Message: "hello",
   451  								Icon:    "H",
   452  							},
   453  							{},
   454  							{},
   455  							{},
   456  							{
   457  								Result:  1,
   458  								CellId:  "cell-9",
   459  								Message: "no",
   460  								Icon:    "N",
   461  							},
   462  						},
   463  					},
   464  				},
   465  			},
   466  		},
   467  		{
   468  			name: "Returns tab from tab state",
   469  			config: map[string]*pb.Configuration{
   470  				"gs://default/config": {
   471  					Dashboards: []*pb.Dashboard{
   472  						{
   473  							Name: "Dashboard1",
   474  							DashboardTab: []*pb.DashboardTab{
   475  								{
   476  									Name:          "tab 1",
   477  									TestGroupName: "testgroupname",
   478  								},
   479  							},
   480  						},
   481  					},
   482  				},
   483  			},
   484  			grid: map[string]*statepb.Grid{
   485  				"gs://default/look-ma-tabs/Dashboard1/tab%201": {
   486  					Rows: []*statepb.Row{
   487  						{
   488  							Name:     "tabrow1",
   489  							Id:       "tabrow1",
   490  							Results:  []int32{1, 2},
   491  							CellIds:  []string{"cell-1", "cell-2"},
   492  							Messages: []string{"tab soda", "", "", ""},
   493  							Icons:    []string{"", "", "", ""},
   494  						},
   495  					},
   496  				},
   497  			},
   498  			patch: func(s *Server) {
   499  				s.TabPathPrefix = "look-ma-tabs"
   500  			},
   501  			req: &apipb.ListRowsRequest{
   502  				Scope:     "gs://default",
   503  				Dashboard: "dashboard1",
   504  				Tab:       "tab1",
   505  			},
   506  			want: &apipb.ListRowsResponse{
   507  				Rows: []*apipb.ListRowsResponse_Row{
   508  					{
   509  						Name: "tabrow1",
   510  						Cells: []*apipb.ListRowsResponse_Cell{
   511  							{
   512  								Result:  1,
   513  								CellId:  "cell-1",
   514  								Message: "tab soda",
   515  							},
   516  							{
   517  								Result: 1,
   518  								CellId: "cell-2",
   519  							},
   520  						},
   521  					},
   522  				},
   523  			},
   524  		},
   525  		{
   526  			name: "Returns tab from state with row info missing",
   527  			config: map[string]*pb.Configuration{
   528  				"gs://default/config": {
   529  					Dashboards: []*pb.Dashboard{
   530  						{
   531  							Name: "Dashboard1",
   532  							DashboardTab: []*pb.DashboardTab{
   533  								{
   534  									Name:          "tab 1",
   535  									TestGroupName: "testgroupname",
   536  								},
   537  							},
   538  						},
   539  					},
   540  				},
   541  			},
   542  			grid: map[string]*statepb.Grid{
   543  				"gs://default/look-ma-tabs/Dashboard1/tab%201": {
   544  					Rows: []*statepb.Row{
   545  						{
   546  							Name:     "tabrow1",
   547  							Id:       "tabrow1",
   548  							Results:  []int32{1, 2},
   549  							CellIds:  []string{"cell-1", "cell-2"},
   550  							Messages: []string{"tab soda", ""},
   551  							Icons:    []string{"", ""},
   552  						},
   553  						{
   554  							Name:    "tabrow1@TESTGRID@method-a",
   555  							Id:      "tabrow1",
   556  							Results: []int32{1, 2},
   557  						},
   558  					},
   559  				},
   560  			},
   561  			patch: func(s *Server) {
   562  				s.TabPathPrefix = "look-ma-tabs"
   563  			},
   564  			req: &apipb.ListRowsRequest{
   565  				Scope:     "gs://default",
   566  				Dashboard: "dashboard1",
   567  				Tab:       "tab1",
   568  			},
   569  			want: &apipb.ListRowsResponse{
   570  				Rows: []*apipb.ListRowsResponse_Row{
   571  					{
   572  						Name: "tabrow1",
   573  						Cells: []*apipb.ListRowsResponse_Cell{
   574  							{
   575  								Result:  1,
   576  								CellId:  "cell-1",
   577  								Message: "tab soda",
   578  							},
   579  							{
   580  								Result: 1,
   581  								CellId: "cell-2",
   582  							},
   583  						},
   584  					},
   585  					{
   586  						Name: "tabrow1@TESTGRID@method-a",
   587  						Cells: []*apipb.ListRowsResponse_Cell{
   588  							{Result: 1},
   589  							{Result: 1},
   590  						},
   591  					},
   592  				},
   593  			},
   594  		},
   595  	}
   596  	for _, tc := range tests {
   597  		t.Run(tc.name, func(t *testing.T) {
   598  			server := setupTestServer(t, tc.config, tc.grid, nil)
   599  			if tc.patch != nil {
   600  				tc.patch(&server)
   601  			}
   602  			got, err := server.ListRows(context.Background(), tc.req)
   603  			switch {
   604  			case err != nil:
   605  				if !tc.err {
   606  					t.Errorf("ListRows() got unexpected error: %v", err)
   607  				}
   608  			case tc.err:
   609  				t.Error("ListRows() failed to receive an error")
   610  			default:
   611  				if diff := cmp.Diff(tc.want, got, protocmp.Transform()); diff != "" {
   612  					t.Errorf("ListRows() got unexpected diff (-want +got):\n%s", diff)
   613  				}
   614  			}
   615  		})
   616  	}
   617  }