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 }