github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/aggregator/tools/deploy/helper_test.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package deploy 22 23 import ( 24 "errors" 25 "fmt" 26 "strings" 27 "testing" 28 "time" 29 30 "github.com/m3db/m3/src/cluster/placement" 31 "github.com/m3db/m3/src/x/retry" 32 33 "github.com/golang/mock/gomock" 34 "github.com/stretchr/testify/require" 35 ) 36 37 var ( 38 testPlacement = placement.NewPlacement().SetInstances( 39 []placement.Instance{ 40 placement.NewInstance(). 41 SetID("placement_instance1"). 42 SetEndpoint("placement_instance1_endpoint"). 43 SetShardSetID(0), 44 placement.NewInstance(). 45 SetID("placement_instance2"). 46 SetEndpoint("placement_instance2_endpoint"). 47 SetShardSetID(0), 48 placement.NewInstance(). 49 SetID("placement_instance3"). 50 SetEndpoint("placement_instance3_endpoint"). 51 SetShardSetID(1), 52 placement.NewInstance(). 53 SetID("placement_instance4"). 54 SetEndpoint("placement_instance4_endpoint"). 55 SetShardSetID(1), 56 }, 57 ) 58 testInstanceMetadatas = instanceMetadatas{ 59 { 60 PlacementInstanceID: "placement_instance1", 61 DeploymentInstanceID: "deployment_instance1", 62 ShardSetID: 0, 63 APIEndpoint: "placement_instance1_endpoint", 64 Revision: "revision1", 65 }, 66 { 67 PlacementInstanceID: "placement_instance2", 68 DeploymentInstanceID: "deployment_instance2", 69 ShardSetID: 0, 70 APIEndpoint: "placement_instance2_endpoint", 71 Revision: "revision2", 72 }, 73 { 74 PlacementInstanceID: "placement_instance3", 75 DeploymentInstanceID: "deployment_instance3", 76 ShardSetID: 1, 77 APIEndpoint: "placement_instance3_endpoint", 78 Revision: "revision3", 79 }, 80 { 81 PlacementInstanceID: "placement_instance4", 82 DeploymentInstanceID: "deployment_instance4", 83 ShardSetID: 1, 84 APIEndpoint: "placement_instance4_endpoint", 85 Revision: "revision4", 86 }, 87 } 88 ) 89 90 func TestHelperDeployEmptyRevision(t *testing.T) { 91 helper := testHelper(t) 92 require.Equal(t, errInvalidRevision, helper.Deploy("", nil, DryRunMode)) 93 } 94 95 func TestHelperGeneratePlanError(t *testing.T) { 96 ctrl := gomock.NewController(t) 97 defer ctrl.Finish() 98 99 instances := testMockInstances(ctrl) 100 mgr := NewMockManager(ctrl) 101 mgr.EXPECT().QueryAll().Return(instances, nil).AnyTimes() 102 103 errGeneratePlan := errors.New("error generating plan") 104 planner := NewMockplanner(ctrl) 105 planner.EXPECT().GeneratePlan(gomock.Any(), gomock.Any()).Return(emptyPlan, errGeneratePlan).AnyTimes() 106 107 helper := testHelper(t) 108 helper.mgr = mgr 109 helper.planner = planner 110 require.Error(t, helper.Deploy("revision4", testPlacement, DryRunMode)) 111 } 112 113 func TestHelperGeneratePlanDryRunMode(t *testing.T) { 114 ctrl := gomock.NewController(t) 115 defer ctrl.Finish() 116 117 var ( 118 filteredRes instanceMetadatas 119 allRes instanceMetadatas 120 ) 121 122 instances := testMockInstances(ctrl) 123 mgr := NewMockManager(ctrl) 124 mgr.EXPECT().QueryAll().Return(instances, nil).AnyTimes() 125 126 planner := NewMockplanner(ctrl) 127 planner.EXPECT(). 128 GeneratePlan(gomock.Any(), gomock.Any()). 129 DoAndReturn(func(toDeploy, all instanceMetadatas) (deploymentPlan, error) { 130 filteredRes = toDeploy 131 allRes = all 132 return emptyPlan, nil 133 }). 134 AnyTimes() 135 136 helper := testHelper(t) 137 helper.mgr = mgr 138 helper.planner = planner 139 require.NoError(t, helper.Deploy("revision4", testPlacement, DryRunMode)) 140 require.Equal(t, testInstanceMetadatas[:3], filteredRes) 141 require.Equal(t, testInstanceMetadatas, allRes) 142 } 143 144 func TestHelperWaitUntilSafeQueryError(t *testing.T) { 145 ctrl := gomock.NewController(t) 146 defer ctrl.Finish() 147 148 errQuery := errors.New("error querying instances") 149 mgr := NewMockManager(ctrl) 150 mgr.EXPECT().Query(gomock.Any()).Return(nil, errQuery).AnyTimes() 151 152 helper := testHelper(t) 153 retryOpts := retry.NewOptions(). 154 SetMaxRetries(3). 155 SetInitialBackoff(10 * time.Millisecond). 156 SetBackoffFactor(1) 157 helper.foreverRetrier = retry.NewRetrier(retryOpts) 158 helper.mgr = mgr 159 require.Error(t, helper.waitUntilSafe(testInstanceMetadatas)) 160 } 161 162 func TestHelperWaitUntilSafeInstanceUnhealthy(t *testing.T) { 163 ctrl := gomock.NewController(t) 164 defer ctrl.Finish() 165 166 var instances []Instance 167 instance1 := NewMockInstance(ctrl) 168 instance1.EXPECT().IsHealthy().Return(false).AnyTimes() 169 instance1.EXPECT().IsDeploying().Return(false).AnyTimes() 170 instances = append(instances, instance1) 171 172 instance2 := NewMockInstance(ctrl) 173 instance2.EXPECT().IsHealthy().Return(false).AnyTimes() 174 instance2.EXPECT().IsDeploying().Return(false).AnyTimes() 175 instances = append(instances, instance2) 176 177 mgr := NewMockManager(ctrl) 178 mgr.EXPECT().Query(gomock.Any()).Return(instances, nil).AnyTimes() 179 180 helper := testHelper(t) 181 retryOpts := retry.NewOptions(). 182 SetMaxRetries(3). 183 SetInitialBackoff(10 * time.Millisecond). 184 SetBackoffFactor(1) 185 helper.foreverRetrier = retry.NewRetrier(retryOpts) 186 helper.mgr = mgr 187 require.Error(t, helper.waitUntilSafe(testInstanceMetadatas[:2])) 188 } 189 190 func TestHelperWaitUntilSafeInstanceIsDeploying(t *testing.T) { 191 ctrl := gomock.NewController(t) 192 defer ctrl.Finish() 193 194 var instances []Instance 195 instance1 := NewMockInstance(ctrl) 196 instance1.EXPECT().IsHealthy().Return(true).AnyTimes() 197 instance1.EXPECT().IsDeploying().Return(true).AnyTimes() 198 instances = append(instances, instance1) 199 200 instance2 := NewMockInstance(ctrl) 201 instance2.EXPECT().IsHealthy().Return(true).AnyTimes() 202 instance2.EXPECT().IsDeploying().Return(false).AnyTimes() 203 instances = append(instances, instance2) 204 205 mgr := NewMockManager(ctrl) 206 mgr.EXPECT().Query(gomock.Any()).Return(instances, nil).AnyTimes() 207 208 helper := testHelper(t) 209 retryOpts := retry.NewOptions(). 210 SetMaxRetries(3). 211 SetInitialBackoff(10 * time.Millisecond). 212 SetBackoffFactor(1) 213 helper.foreverRetrier = retry.NewRetrier(retryOpts) 214 helper.mgr = mgr 215 216 client := NewMockAggregatorClient(ctrl) 217 client.EXPECT().IsHealthy(gomock.Any()).Return(nil).AnyTimes() 218 helper.client = client 219 require.Error(t, helper.waitUntilSafe(testInstanceMetadatas[:2])) 220 } 221 222 func TestHelperWaitUntilSafeInstanceUnhealthyFromAPI(t *testing.T) { 223 ctrl := gomock.NewController(t) 224 defer ctrl.Finish() 225 226 errInstanceUnhealthy := errors.New("instance is not healthy") 227 var instances []Instance 228 instance1 := NewMockInstance(ctrl) 229 instance1.EXPECT().IsHealthy().Return(true).AnyTimes() 230 instance1.EXPECT().IsDeploying().Return(false).AnyTimes() 231 instances = append(instances, instance1) 232 233 instance2 := NewMockInstance(ctrl) 234 instance2.EXPECT().IsHealthy().Return(true).AnyTimes() 235 instance2.EXPECT().IsDeploying().Return(false).AnyTimes() 236 instances = append(instances, instance2) 237 238 mgr := NewMockManager(ctrl) 239 mgr.EXPECT().Query(gomock.Any()).Return(instances, nil).AnyTimes() 240 241 helper := testHelper(t) 242 retryOpts := retry.NewOptions(). 243 SetMaxRetries(3). 244 SetInitialBackoff(10 * time.Millisecond). 245 SetBackoffFactor(1) 246 helper.foreverRetrier = retry.NewRetrier(retryOpts) 247 helper.mgr = mgr 248 249 client := NewMockAggregatorClient(ctrl) 250 client.EXPECT().IsHealthy(gomock.Any()).Return(errInstanceUnhealthy).AnyTimes() 251 helper.client = client 252 require.Error(t, helper.waitUntilSafe(testInstanceMetadatas[:2])) 253 } 254 255 func TestHelperWaitUntilSafeSuccess(t *testing.T) { 256 ctrl := gomock.NewController(t) 257 defer ctrl.Finish() 258 259 var instances []Instance 260 instance1 := NewMockInstance(ctrl) 261 instance1.EXPECT().IsHealthy().Return(true).AnyTimes() 262 instance1.EXPECT().IsDeploying().Return(false).AnyTimes() 263 instances = append(instances, instance1) 264 265 instance2 := NewMockInstance(ctrl) 266 instance2.EXPECT().IsHealthy().Return(true).AnyTimes() 267 instance2.EXPECT().IsDeploying().Return(false).AnyTimes() 268 instances = append(instances, instance2) 269 270 mgr := NewMockManager(ctrl) 271 mgr.EXPECT().Query(gomock.Any()).Return(instances, nil).AnyTimes() 272 273 helper := testHelper(t) 274 helper.mgr = mgr 275 276 client := NewMockAggregatorClient(ctrl) 277 client.EXPECT().IsHealthy(gomock.Any()).Return(nil).AnyTimes() 278 helper.client = client 279 require.NoError(t, helper.waitUntilSafe(testInstanceMetadatas[:2])) 280 } 281 282 func TestHelperValidateError(t *testing.T) { 283 errValidate := errors.New("error validating") 284 targets := deploymentTargets{ 285 {Validator: func() error { return errValidate }}, 286 {Validator: func() error { return errValidate }}, 287 } 288 helper := testHelper(t) 289 retryOpts := retry.NewOptions(). 290 SetMaxRetries(3). 291 SetInitialBackoff(10 * time.Millisecond). 292 SetBackoffFactor(1) 293 helper.foreverRetrier = retry.NewRetrier(retryOpts) 294 require.Error(t, helper.validate(targets)) 295 } 296 297 func TestHelperValidateSuccess(t *testing.T) { 298 targets := deploymentTargets{ 299 {Validator: func() error { return nil }}, 300 {Validator: func() error { return nil }}, 301 } 302 helper := testHelper(t) 303 require.NoError(t, helper.validate(targets)) 304 } 305 306 func TestHelperResignError(t *testing.T) { 307 ctrl := gomock.NewController(t) 308 defer ctrl.Finish() 309 310 errResign := errors.New("error resigning") 311 targets := deploymentTargets{ 312 {Instance: testInstanceMetadatas[0]}, 313 {Instance: testInstanceMetadatas[1]}, 314 } 315 helper := testHelper(t) 316 retryOpts := retry.NewOptions(). 317 SetMaxRetries(3). 318 SetInitialBackoff(10 * time.Millisecond). 319 SetBackoffFactor(1) 320 helper.retrier = retry.NewRetrier(retryOpts) 321 322 client := NewMockAggregatorClient(ctrl) 323 client.EXPECT().Resign(gomock.Any()).Return(errResign).AnyTimes() 324 helper.client = client 325 require.Error(t, helper.resign(targets)) 326 } 327 328 func TestHelperResignSuccess(t *testing.T) { 329 ctrl := gomock.NewController(t) 330 defer ctrl.Finish() 331 332 targets := deploymentTargets{ 333 {Instance: testInstanceMetadatas[0]}, 334 {Instance: testInstanceMetadatas[1]}, 335 } 336 helper := testHelper(t) 337 client := NewMockAggregatorClient(ctrl) 338 client.EXPECT().Resign(gomock.Any()).Return(nil).AnyTimes() 339 helper.client = client 340 require.NoError(t, helper.resign(targets)) 341 } 342 343 func TestHelperWaitUntilProgressingQueryError(t *testing.T) { 344 ctrl := gomock.NewController(t) 345 defer ctrl.Finish() 346 347 targetIDs := []string{"instance1", "instance2"} 348 revision := "revision1" 349 helper := testHelper(t) 350 retryOpts := retry.NewOptions(). 351 SetMaxRetries(3). 352 SetInitialBackoff(10 * time.Millisecond). 353 SetBackoffFactor(1) 354 helper.foreverRetrier = retry.NewRetrier(retryOpts) 355 356 errQuery := errors.New("error querying instances") 357 mgr := NewMockManager(ctrl) 358 mgr.EXPECT().Query(gomock.Any()).Return(nil, errQuery).AnyTimes() 359 helper.mgr = mgr 360 require.Error(t, helper.waitUntilProgressing(targetIDs, revision)) 361 } 362 363 func TestHelperWaitUntilProgressingInstanceNotProgressing(t *testing.T) { 364 ctrl := gomock.NewController(t) 365 defer ctrl.Finish() 366 367 targetIDs := []string{"instance1", "instance2"} 368 revision := "revision2" 369 370 var instances []Instance 371 instance1 := NewMockInstance(ctrl) 372 instance1.EXPECT().IsDeploying().Return(false).AnyTimes() 373 instance1.EXPECT().Revision().Return("revision1").AnyTimes() 374 instances = append(instances, instance1) 375 376 instance2 := NewMockInstance(ctrl) 377 instance2.EXPECT().IsDeploying().Return(false).AnyTimes() 378 instance2.EXPECT().Revision().Return("revision1").AnyTimes() 379 instances = append(instances, instance2) 380 381 helper := testHelper(t) 382 retryOpts := retry.NewOptions(). 383 SetMaxRetries(3). 384 SetInitialBackoff(10 * time.Millisecond). 385 SetBackoffFactor(1) 386 helper.foreverRetrier = retry.NewRetrier(retryOpts) 387 388 mgr := NewMockManager(ctrl) 389 mgr.EXPECT().Query(gomock.Any()).Return(instances, nil).AnyTimes() 390 helper.mgr = mgr 391 require.Error(t, helper.waitUntilProgressing(targetIDs, revision)) 392 } 393 394 func TestHelperWaitUntilProgressingInstanceIsDeploying(t *testing.T) { 395 ctrl := gomock.NewController(t) 396 defer ctrl.Finish() 397 398 targetIDs := []string{"instance1", "instance2"} 399 revision := "revision2" 400 401 var instances []Instance 402 instance1 := NewMockInstance(ctrl) 403 instance1.EXPECT().IsDeploying().Return(true).AnyTimes() 404 instance1.EXPECT().Revision().Return("revision1").AnyTimes() 405 instances = append(instances, instance1) 406 407 instance2 := NewMockInstance(ctrl) 408 instance2.EXPECT().IsDeploying().Return(false).AnyTimes() 409 instance2.EXPECT().Revision().Return("revision1").AnyTimes() 410 instances = append(instances, instance2) 411 412 mgr := NewMockManager(ctrl) 413 mgr.EXPECT().Query(gomock.Any()).Return(instances, nil).AnyTimes() 414 helper := testHelper(t) 415 helper.mgr = mgr 416 require.NoError(t, helper.waitUntilProgressing(targetIDs, revision)) 417 } 418 419 func TestHelperWaitUntilProgressingInstanceIsDeployed(t *testing.T) { 420 ctrl := gomock.NewController(t) 421 defer ctrl.Finish() 422 423 targetIDs := []string{"instance1", "instance2"} 424 revision := "revision2" 425 426 var instances []Instance 427 instance1 := NewMockInstance(ctrl) 428 instance1.EXPECT().IsDeploying().Return(false).AnyTimes() 429 instance1.EXPECT().Revision().Return("revision2").AnyTimes() 430 instances = append(instances, instance1) 431 432 instance2 := NewMockInstance(ctrl) 433 instance2.EXPECT().IsDeploying().Return(false).AnyTimes() 434 instance2.EXPECT().Revision().Return("revision1").AnyTimes() 435 instances = append(instances, instance2) 436 437 mgr := NewMockManager(ctrl) 438 mgr.EXPECT().Query(gomock.Any()).Return(instances, nil).AnyTimes() 439 helper := testHelper(t) 440 helper.mgr = mgr 441 require.NoError(t, helper.waitUntilProgressing(targetIDs, revision)) 442 } 443 444 func TestHelperAllInstanceMetadatasManagerQueryAllError(t *testing.T) { 445 ctrl := gomock.NewController(t) 446 defer ctrl.Finish() 447 448 errQueryAll := errors.New("query all error") 449 mgr := NewMockManager(ctrl) 450 mgr.EXPECT().QueryAll().Return(nil, errQueryAll).AnyTimes() 451 helper := testHelper(t) 452 helper.mgr = mgr 453 _, err := helper.allInstanceMetadatas(testPlacement) 454 require.Error(t, err) 455 } 456 457 func TestHelperAllInstanceMetadatasNumInstancesMismatch(t *testing.T) { 458 ctrl := gomock.NewController(t) 459 defer ctrl.Finish() 460 461 var instances []Instance 462 instance1 := NewMockInstance(ctrl) 463 instance1.EXPECT().ID().Return("instance1").AnyTimes() 464 instances = append(instances, instance1) 465 466 mgr := NewMockManager(ctrl) 467 mgr.EXPECT().QueryAll().Return(instances, nil).AnyTimes() 468 469 helper := testHelper(t) 470 helper.mgr = mgr 471 _, err := helper.allInstanceMetadatas(testPlacement) 472 require.Error(t, err) 473 } 474 475 func TestHelperAllInstanceMetadatasToAPIEndpointFnError(t *testing.T) { 476 ctrl := gomock.NewController(t) 477 defer ctrl.Finish() 478 479 errToAPIEndpoint := errors.New("error converting to api endpoint") 480 instances := testMockInstances(ctrl) 481 mgr := NewMockManager(ctrl) 482 mgr.EXPECT().QueryAll().Return(instances, nil).AnyTimes() 483 helper := testHelper(t) 484 helper.mgr = mgr 485 helper.toAPIEndpointFn = func(string) (string, error) { return "", errToAPIEndpoint } 486 _, err := helper.allInstanceMetadatas(testPlacement) 487 require.Error(t, err) 488 } 489 490 func TestHelperAllInstanceMetadatasToPlacementInstanceIDFnError(t *testing.T) { 491 ctrl := gomock.NewController(t) 492 defer ctrl.Finish() 493 494 errToPlacementInstanceID := errors.New("error converting to placement instance id") 495 instances := testMockInstances(ctrl) 496 mgr := NewMockManager(ctrl) 497 mgr.EXPECT().QueryAll().Return(instances, nil).AnyTimes() 498 helper := testHelper(t) 499 helper.mgr = mgr 500 helper.toPlacementInstanceIDFn = func(string) (string, error) { return "", errToPlacementInstanceID } 501 _, err := helper.allInstanceMetadatas(testPlacement) 502 require.Error(t, err) 503 } 504 505 func TestHelperAllInstanceMetadatasDuplicateDeploymentInstance(t *testing.T) { 506 ctrl := gomock.NewController(t) 507 defer ctrl.Finish() 508 509 var instances []Instance 510 instances = append(instances, testMockInstances(ctrl)...) 511 instances[0] = instances[1] 512 513 mgr := NewMockManager(ctrl) 514 mgr.EXPECT().QueryAll().Return(instances, nil).AnyTimes() 515 helper := testHelper(t) 516 helper.mgr = mgr 517 518 _, err := helper.allInstanceMetadatas(testPlacement) 519 require.Error(t, err) 520 } 521 522 func TestHelperAllInstanceMetadatasDeploymentInstanceNotExist(t *testing.T) { 523 ctrl := gomock.NewController(t) 524 defer ctrl.Finish() 525 526 var instances []Instance 527 instances = append(instances, testMockInstances(ctrl)...) 528 instance := NewMockInstance(ctrl) 529 instance.EXPECT().ID().Return("deployment_instance5").AnyTimes() 530 instance.EXPECT().Revision().Return("revision5").AnyTimes() 531 instances[3] = instance 532 533 mgr := NewMockManager(ctrl) 534 mgr.EXPECT().QueryAll().Return(instances, nil).AnyTimes() 535 helper := testHelper(t) 536 helper.mgr = mgr 537 538 _, err := helper.allInstanceMetadatas(testPlacement) 539 require.Error(t, err) 540 } 541 542 func TestHelperAllInstanceMetadatasSuccess(t *testing.T) { 543 ctrl := gomock.NewController(t) 544 defer ctrl.Finish() 545 546 instances := testMockInstances(ctrl) 547 mgr := NewMockManager(ctrl) 548 mgr.EXPECT().QueryAll().Return(instances, nil).AnyTimes() 549 helper := testHelper(t) 550 helper.mgr = mgr 551 552 res, err := helper.allInstanceMetadatas(testPlacement) 553 require.NoError(t, err) 554 require.Equal(t, testInstanceMetadatas, res) 555 } 556 557 func TestInstanceMetadatasDeploymentInstanceIDs(t *testing.T) { 558 expectedIDs := []string{ 559 "deployment_instance1", 560 "deployment_instance2", 561 "deployment_instance3", 562 "deployment_instance4", 563 } 564 require.Equal(t, expectedIDs, testInstanceMetadatas.DeploymentInstanceIDs()) 565 } 566 567 func TestInstanceMetadatasFilter(t *testing.T) { 568 require.Equal(t, instanceMetadatas(testInstanceMetadatas[1:]), testInstanceMetadatas.WithoutRevision("revision1")) 569 } 570 571 func testMockInstances(ctrl *gomock.Controller) []Instance { 572 instances := make([]Instance, 4) 573 for i := 1; i <= 4; i++ { 574 instance := NewMockInstance(ctrl) 575 instance.EXPECT().ID().Return(fmt.Sprintf("deployment_instance%d", i)).AnyTimes() 576 instance.EXPECT().Revision().Return(fmt.Sprintf("revision%d", i)).AnyTimes() 577 instances[i-1] = instance 578 } 579 return instances 580 } 581 582 func testHelper(t *testing.T) helper { 583 toAPIEndpointFn := func(endpoint string) (string, error) { return endpoint, nil } 584 toPlacementInstanceIDFn := func(id string) (string, error) { 585 converted := strings.Replace(id, "deployment", "placement", -1) 586 return converted, nil 587 } 588 opts := NewHelperOptions(). 589 SetPlannerOptions(NewPlannerOptions()). 590 SetToAPIEndpointFn(toAPIEndpointFn). 591 SetToPlacementInstanceIDFn(toPlacementInstanceIDFn) 592 res, err := NewHelper(opts) 593 require.NoError(t, err) 594 return res.(helper) 595 }