github.com/GoogleCloudPlatform/compute-image-tools/cli_tools@v0.0.0-20240516224744-de2dabc4ed1b/common/disk/inspect_test.go (about)

     1  //  Copyright 2020 Google Inc. All Rights Reserved.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  package disk
    16  
    17  import (
    18  	"encoding/base64"
    19  	"errors"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/golang/mock/gomock"
    24  	"github.com/golang/protobuf/proto"
    25  	"github.com/google/go-cmp/cmp"
    26  	"github.com/stretchr/testify/assert"
    27  	"google.golang.org/protobuf/testing/protocmp"
    28  
    29  	"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/logging"
    30  	"github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/mocks"
    31  	"github.com/GoogleCloudPlatform/compute-image-tools/proto/go/pb"
    32  )
    33  
    34  func TestBootInspector_Inspect_PassesVarsWhenInvokingWorkflow(t *testing.T) {
    35  	reference := "uri/for/pd"
    36  
    37  	logger := logging.NewToolLogger(t.Name())
    38  	expected := &pb.InspectionResults{
    39  		UefiBootable: true,
    40  	}
    41  
    42  	mockCtrl := gomock.NewController(t)
    43  	defer mockCtrl.Finish()
    44  	worker := mocks.NewMockDaisyWorker(mockCtrl)
    45  	worker.EXPECT().RunAndReadSerialValue("inspect_pb", map[string]string{
    46  		"pd_uri": reference,
    47  	}).Return(encodeToBase64(expected), nil)
    48  	inspector := bootInspector{worker, logger}
    49  
    50  	actual, err := inspector.Inspect(reference)
    51  	assert.NoError(t, err)
    52  	assertLogsContainResults(t, expected, logger)
    53  	actual.ElapsedTimeMs = 0
    54  	if diff := cmp.Diff(expected, actual, protocmp.Transform()); diff != "" {
    55  		t.Errorf("unexpected difference:\n%v", diff)
    56  	}
    57  }
    58  
    59  func TestBootInspector_Inspect_WorkerAndTransitErrors(t *testing.T) {
    60  	for _, tt := range []struct {
    61  		caseName             string
    62  		base64FromInspection string
    63  		errorFromInspection  error
    64  		expectResults        *pb.InspectionResults
    65  		expectErrorToContain string
    66  	}{
    67  		{
    68  			caseName:            "worker fails to run",
    69  			errorFromInspection: errors.New("failure-from-daisy"),
    70  			expectResults: &pb.InspectionResults{
    71  				ErrorWhen: pb.InspectionResults_RUNNING_WORKER,
    72  			},
    73  			expectErrorToContain: "failure-from-daisy",
    74  		}, {
    75  			caseName:             "worker returns invalid base64",
    76  			base64FromInspection: "garbage",
    77  			expectResults: &pb.InspectionResults{
    78  				ErrorWhen: pb.InspectionResults_DECODING_WORKER_RESPONSE,
    79  			},
    80  			expectErrorToContain: "base64",
    81  		}, {
    82  			caseName:             "worker returns invalid proto bytes",
    83  			base64FromInspection: base64.StdEncoding.EncodeToString([]byte("garbage")),
    84  			expectResults: &pb.InspectionResults{
    85  				ErrorWhen: pb.InspectionResults_DECODING_WORKER_RESPONSE,
    86  			},
    87  			expectErrorToContain: "cannot parse",
    88  		},
    89  	} {
    90  		t.Run(tt.caseName, func(t *testing.T) {
    91  			mockCtrl := gomock.NewController(t)
    92  			defer mockCtrl.Finish()
    93  			worker := mocks.NewMockDaisyWorker(mockCtrl)
    94  			worker.EXPECT().RunAndReadSerialValue("inspect_pb", map[string]string{
    95  				"pd_uri": "reference",
    96  			}).Return(tt.base64FromInspection, tt.errorFromInspection)
    97  			inspector := bootInspector{worker, logging.NewToolLogger(t.Name())}
    98  			actual, err := inspector.Inspect("reference")
    99  			if err == nil {
   100  				t.Fatal("err must be non-nil")
   101  			}
   102  			assert.Contains(t, err.Error(), tt.expectErrorToContain)
   103  			actual.ElapsedTimeMs = 0
   104  			if diff := cmp.Diff(tt.expectResults, actual, protocmp.Transform()); diff != "" {
   105  				t.Errorf("unexpected difference:\n%v", diff)
   106  			}
   107  		})
   108  	}
   109  }
   110  
   111  func TestBootInspector_Inspect_InvalidWorkerResponses(t *testing.T) {
   112  	for _, tt := range []struct {
   113  		caseName               string
   114  		responseFromInspection *pb.InspectionResults
   115  		expectResults          *pb.InspectionResults
   116  		expectErrorToContain   string
   117  	}{
   118  		{
   119  			caseName: "Fail when OsCount is zero and OsRelease non-nil",
   120  			responseFromInspection: &pb.InspectionResults{
   121  				OsCount:   0,
   122  				OsRelease: &pb.OsRelease{},
   123  			},
   124  			expectResults: &pb.InspectionResults{
   125  				ErrorWhen: pb.InspectionResults_INTERPRETING_INSPECTION_RESULTS,
   126  				OsRelease: &pb.OsRelease{},
   127  			},
   128  			expectErrorToContain: "worker should not return OsRelease when NumOsFound != 1",
   129  		},
   130  		{
   131  			caseName: "Fail when OsCount is one and OsRelease is nil",
   132  			responseFromInspection: &pb.InspectionResults{
   133  				OsCount: 1,
   134  			},
   135  			expectResults: &pb.InspectionResults{
   136  				OsCount:   1,
   137  				ErrorWhen: pb.InspectionResults_INTERPRETING_INSPECTION_RESULTS,
   138  			},
   139  			expectErrorToContain: "worker should return OsRelease when OsCount == 1",
   140  		},
   141  		{
   142  			caseName: "Fail when OsCount > 1 and OsRelease non-nil",
   143  			responseFromInspection: &pb.InspectionResults{
   144  				OsCount:   2,
   145  				OsRelease: &pb.OsRelease{},
   146  			},
   147  			expectResults: &pb.InspectionResults{
   148  				OsCount:   2,
   149  				ErrorWhen: pb.InspectionResults_INTERPRETING_INSPECTION_RESULTS,
   150  				OsRelease: &pb.OsRelease{},
   151  			},
   152  			expectErrorToContain: "worker should not return OsRelease when NumOsFound != 1",
   153  		},
   154  		{
   155  			caseName: "Fail when CliFormatted is populated",
   156  			responseFromInspection: &pb.InspectionResults{
   157  				OsCount: 1,
   158  				OsRelease: &pb.OsRelease{
   159  					Architecture: pb.Architecture_X64,
   160  					MajorVersion: "18",
   161  					MinorVersion: "04",
   162  					DistroId:     pb.Distro_UBUNTU,
   163  					CliFormatted: "ubuntu-1804",
   164  				},
   165  			},
   166  			expectResults: &pb.InspectionResults{
   167  				ErrorWhen: pb.InspectionResults_INTERPRETING_INSPECTION_RESULTS,
   168  				OsCount:   1,
   169  				OsRelease: &pb.OsRelease{
   170  					Architecture: pb.Architecture_X64,
   171  					MajorVersion: "18",
   172  					MinorVersion: "04",
   173  					DistroId:     pb.Distro_UBUNTU,
   174  					CliFormatted: "ubuntu-1804",
   175  				},
   176  			},
   177  			expectErrorToContain: "worker should not return CliFormatted",
   178  		}, {
   179  			caseName: "Fail when Distro name is populated",
   180  			responseFromInspection: &pb.InspectionResults{
   181  				OsCount: 1,
   182  				OsRelease: &pb.OsRelease{
   183  					Architecture: pb.Architecture_X64,
   184  					MajorVersion: "10",
   185  					DistroId:     pb.Distro_UBUNTU,
   186  					Distro:       "ubuntu",
   187  				},
   188  			},
   189  			expectResults: &pb.InspectionResults{
   190  				ErrorWhen: pb.InspectionResults_INTERPRETING_INSPECTION_RESULTS,
   191  				OsCount:   1,
   192  				OsRelease: &pb.OsRelease{
   193  					Architecture: pb.Architecture_X64,
   194  					MajorVersion: "10",
   195  					DistroId:     pb.Distro_UBUNTU,
   196  					Distro:       "ubuntu",
   197  				},
   198  			},
   199  			expectErrorToContain: "worker should not return Distro name",
   200  		}, {
   201  			caseName: "Fail when missing MajorVersion",
   202  			responseFromInspection: &pb.InspectionResults{
   203  				OsCount: 1,
   204  				OsRelease: &pb.OsRelease{
   205  					Architecture: pb.Architecture_X64,
   206  					DistroId:     pb.Distro_UBUNTU,
   207  				},
   208  			},
   209  			expectResults: &pb.InspectionResults{
   210  				ErrorWhen: pb.InspectionResults_INTERPRETING_INSPECTION_RESULTS,
   211  				OsCount:   1,
   212  				OsRelease: &pb.OsRelease{
   213  					Architecture: pb.Architecture_X64,
   214  					DistroId:     pb.Distro_UBUNTU,
   215  				},
   216  			},
   217  			expectErrorToContain: "missing MajorVersion",
   218  		}, {
   219  			caseName: "Fail when missing Architecture",
   220  			responseFromInspection: &pb.InspectionResults{
   221  				OsCount: 1,
   222  				OsRelease: &pb.OsRelease{
   223  					DistroId:     pb.Distro_UBUNTU,
   224  					MajorVersion: "10",
   225  				},
   226  			},
   227  			expectResults: &pb.InspectionResults{
   228  				ErrorWhen: pb.InspectionResults_INTERPRETING_INSPECTION_RESULTS,
   229  				OsCount:   1,
   230  				OsRelease: &pb.OsRelease{
   231  					DistroId:     pb.Distro_UBUNTU,
   232  					MajorVersion: "10",
   233  				},
   234  			},
   235  			expectErrorToContain: "missing Architecture",
   236  		}, {
   237  			caseName: "Fail when missing DistroId",
   238  			responseFromInspection: &pb.InspectionResults{
   239  				OsCount: 1,
   240  				OsRelease: &pb.OsRelease{
   241  					Architecture: pb.Architecture_X64,
   242  					MajorVersion: "10",
   243  				},
   244  			},
   245  			expectResults: &pb.InspectionResults{
   246  				ErrorWhen: pb.InspectionResults_INTERPRETING_INSPECTION_RESULTS,
   247  				OsCount:   1,
   248  				OsRelease: &pb.OsRelease{
   249  					Architecture: pb.Architecture_X64,
   250  					MajorVersion: "10",
   251  				},
   252  			},
   253  			expectErrorToContain: "missing DistroId",
   254  		},
   255  	} {
   256  		t.Run(tt.caseName, func(t *testing.T) {
   257  			logger := logging.NewToolLogger(t.Name())
   258  			mockCtrl := gomock.NewController(t)
   259  			defer mockCtrl.Finish()
   260  			worker := mocks.NewMockDaisyWorker(mockCtrl)
   261  			worker.EXPECT().RunAndReadSerialValue("inspect_pb", map[string]string{
   262  				"pd_uri": "reference",
   263  			}).Return(encodeToBase64(tt.responseFromInspection), nil)
   264  			inspector := bootInspector{worker, logger}
   265  			results, err := inspector.Inspect("reference")
   266  			if err == nil {
   267  				t.Fatal("err must be non-nil")
   268  			}
   269  			assert.Contains(t, err.Error(), tt.expectErrorToContain)
   270  			assertLogsContainResults(t, tt.responseFromInspection, logger)
   271  			results.ElapsedTimeMs = 0
   272  			if diff := cmp.Diff(tt.expectResults, results, protocmp.Transform()); diff != "" {
   273  				t.Errorf("unexpected difference:\n%v", diff)
   274  			}
   275  		})
   276  	}
   277  }
   278  
   279  func TestBootInspector_ForwardsCancelToWorkflow(t *testing.T) {
   280  	for _, tt := range []struct {
   281  		name      string
   282  		reason    string
   283  		cancelled bool
   284  	}{
   285  		{"cancel success", "reason 1", true},
   286  		{"cancel failed", "reason 2", false},
   287  	} {
   288  		t.Run(tt.name, func(t *testing.T) {
   289  			mockCtrl := gomock.NewController(t)
   290  			defer mockCtrl.Finish()
   291  			worker := mocks.NewMockDaisyWorker(mockCtrl)
   292  			worker.EXPECT().Cancel(tt.reason).Return(tt.cancelled)
   293  			inspector := bootInspector{worker, logging.NewToolLogger(t.Name())}
   294  			assert.Equal(t, tt.cancelled, inspector.Cancel(tt.reason))
   295  		})
   296  	}
   297  }
   298  
   299  func encodeToBase64(results *pb.InspectionResults) string {
   300  	if results == nil {
   301  		return ""
   302  	}
   303  	bytes, err := proto.Marshal(results)
   304  	if err != nil {
   305  		panic(err)
   306  	}
   307  	return base64.StdEncoding.EncodeToString(bytes)
   308  }
   309  
   310  func assertLogsContainResults(t *testing.T, results *pb.InspectionResults, logger logging.ToolLogger) {
   311  	var traceIncludesResults bool
   312  	logs := logger.ReadOutputInfo().SerialOutputs
   313  	resultString := results.String()
   314  	for _, log := range logs {
   315  		if strings.Contains(log, resultString) {
   316  			traceIncludesResults = true
   317  			break
   318  		}
   319  	}
   320  	if !traceIncludesResults {
   321  		t.Errorf("Trace logs didn't include results.\n Logs:%#v\n Results: %v", logs, resultString)
   322  	}
   323  }