1 /* 2 * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 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 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.smithy.go.codegen.integration; 17 18 import java.util.ArrayList; 19 import java.util.Comparator; 20 import java.util.List; 21 import java.util.Map; 22 import java.util.Objects; 23 import java.util.Set; 24 import java.util.TreeSet; 25 import java.util.function.Consumer; 26 import java.util.logging.Logger; 27 import software.amazon.smithy.codegen.core.CodegenException; 28 import software.amazon.smithy.codegen.core.Symbol; 29 import software.amazon.smithy.codegen.core.SymbolProvider; 30 import software.amazon.smithy.go.codegen.GoWriter; 31 import software.amazon.smithy.go.codegen.ShapeValueGenerator; 32 import software.amazon.smithy.go.codegen.SmithyGoDependency; 33 import software.amazon.smithy.model.Model; 34 import software.amazon.smithy.model.node.ObjectNode; 35 import software.amazon.smithy.model.shapes.MemberShape; 36 import software.amazon.smithy.model.shapes.OperationShape; 37 import software.amazon.smithy.model.shapes.ServiceShape; 38 import software.amazon.smithy.model.shapes.ShapeId; 39 import software.amazon.smithy.model.shapes.StructureShape; 40 import software.amazon.smithy.model.traits.IdempotencyTokenTrait; 41 import software.amazon.smithy.protocoltests.traits.HttpMessageTestCase; 42 import software.amazon.smithy.utils.SmithyBuilder; 43 44 /** 45 * Abstract base implementation for protocol test generators to extend in order to generate HttpMessageTestCase 46 * specific protocol tests. 47 * 48 * @param <T> Specific HttpMessageTestCase the protocol test generator is for. 49 */ 50 public abstract class HttpProtocolUnitTestGenerator<T extends HttpMessageTestCase> { 51 private static final Logger LOGGER = Logger.getLogger(HttpProtocolUnitTestGenerator.class.getName()); 52 53 protected final Model model; 54 protected final SymbolProvider symbolProvider; 55 protected final List<T> testCases; 56 protected final ServiceShape service; 57 protected final OperationShape operation; 58 protected final Symbol opSymbol; 59 protected final StructureShape inputShape; 60 protected final Symbol inputSymbol; 61 protected final StructureShape outputShape; 62 protected final Symbol outputSymbol; 63 protected final String protocolName; 64 protected final Set<ConfigValue> clientConfigValues = new TreeSet<>(); 65 protected final Set<SkipTest> skipTests = new TreeSet<>(); 66 protected final ShapeValueGenerator.Config shapeValueGeneratorConfig; 67 68 /** 69 * Initializes the abstract protocol tests generator. 70 * 71 * @param builder the builder initializing the generator. 72 */ HttpProtocolUnitTestGenerator(Builder<T> builder)73 protected HttpProtocolUnitTestGenerator(Builder<T> builder) { 74 this.model = SmithyBuilder.requiredState("model", builder.model); 75 this.symbolProvider = SmithyBuilder.requiredState("symbolProvider", builder.symbolProvider); 76 this.protocolName = SmithyBuilder.requiredState("protocolName", builder.protocolName); 77 this.service = SmithyBuilder.requiredState("service", builder.service); 78 this.operation = SmithyBuilder.requiredState("operation", builder.operation); 79 this.testCases = SmithyBuilder.requiredState("testCases", builder.testCases); 80 this.clientConfigValues.addAll(builder.clientConfigValues); 81 this.skipTests.addAll(builder.skipTests); 82 this.shapeValueGeneratorConfig = SmithyBuilder.requiredState("config", builder.shapeValueGeneratorConfig); 83 84 opSymbol = symbolProvider.toSymbol(operation); 85 86 inputShape = model.expectShape(operation.getInput() 87 .orElseThrow(() -> new CodegenException("missing input shape for operation: " + operation.getId())), 88 StructureShape.class); 89 inputSymbol = symbolProvider.toSymbol(inputShape); 90 91 outputShape = model.expectShape(operation.getOutput() 92 .orElseThrow(() -> new CodegenException("missing output shape for operation: " + operation.getId())), 93 StructureShape.class); 94 outputSymbol = symbolProvider.toSymbol(outputShape); 95 } 96 97 /** 98 * Provides the unit test function's format string. 99 * 100 * @return returns format string paired with unitTestFuncNameArgs 101 */ unitTestFuncNameFormat()102 abstract String unitTestFuncNameFormat(); 103 104 /** 105 * Provides the unit test function name's format string arguments. 106 * 107 * @return returns a list of arguments used to format the unitTestFuncNameFormat returned format string. 108 */ unitTestFuncNameArgs()109 abstract Object[] unitTestFuncNameArgs(); 110 111 112 /** 113 * Hook to provide custom generated code within a test function before test cases are defined. 114 * 115 * @param writer writer to write generated code with. 116 */ generateTestSetup(GoWriter writer)117 protected void generateTestSetup(GoWriter writer) { 118 // Pass 119 } 120 121 /** 122 * Hook to generate the parameter declarations as struct parameters into the test case's struct definition. 123 * Must generate all test case parameters before returning. 124 * 125 * @param writer writer to write generated code with. 126 */ generateTestCaseParams(GoWriter writer)127 abstract void generateTestCaseParams(GoWriter writer); 128 129 /** 130 * Hook to generate all the test case parameters as struct member values for a single test case. 131 * Must generate all test case parameter values before returning. 132 * 133 * @param writer writer to write generated code with. 134 * @param testCase definition of a single test case. 135 */ generateTestCaseValues(GoWriter writer, T testCase)136 abstract void generateTestCaseValues(GoWriter writer, T testCase); 137 138 /** 139 * Hook to optionally generate additional setup needed before the test body is created. 140 * 141 * @param writer writer to write generated code with. 142 */ generateTestBodySetup(GoWriter writer)143 protected void generateTestBodySetup(GoWriter writer) { 144 // pass 145 } 146 147 /** 148 * Hook to generate the HTTP response body of the protocol test. If overriding and delegating to this method must 149 * the last usage of ResponseWriter. 150 * 151 * @param writer writer to write generated code with. 152 */ generateTestServerHandler(GoWriter writer)153 protected void generateTestServerHandler(GoWriter writer) { 154 writer.write("w.WriteHeader(200)"); 155 } 156 157 /** 158 * Hook to generate the HTTP test server that will receive requests and provide responses back to the requester. 159 * 160 * @param writer writer to write generated code with. 161 * @param name test server variable name 162 * @param handler lambda for writing handling of HTTP request 163 */ generateTestServer(GoWriter writer, String name, Consumer<GoWriter> handler)164 protected void generateTestServer(GoWriter writer, String name, Consumer<GoWriter> handler) { 165 writer.addUseImports(SmithyGoDependency.NET_HTTP); 166 writer.addUseImports(SmithyGoDependency.NET_HTTP_TEST); 167 writer.openBlock("$L := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){", 168 "}))", name, () -> { 169 handler.accept(writer); 170 }); 171 writer.write("defer $L.Close()", name); 172 writer.write("url := server.URL"); 173 } 174 175 /** 176 * Hook to generate the instance of the API client for the protocol test. 177 * 178 * @param writer writer to write generated code with. 179 * @param clientName test client variable name 180 */ generateTestClient(GoWriter writer, String clientName)181 protected void generateTestClient(GoWriter writer, String clientName) { 182 writer.openBlock("$L := New(Options{", "})", clientName, () -> { 183 for (ConfigValue value : clientConfigValues) { 184 writeStructField(writer, value.getName(), value.getValue()); 185 } 186 }); 187 } 188 189 /** 190 * Hook to generate the client invoking the API operation of the test. Should not do any assertions. 191 * 192 * @param writer writer to write generated code with. 193 * @param clientName name of the client variable. 194 */ generateTestInvokeClientOperation(GoWriter writer, String clientName)195 abstract void generateTestInvokeClientOperation(GoWriter writer, String clientName); 196 197 /** 198 * Hook to generate the assertions for the operation's test cases. Will be in the same scope as the test body. 199 * 200 * @param writer writer to write generated code with. 201 */ generateTestAssertions(GoWriter writer)202 abstract void generateTestAssertions(GoWriter writer); 203 204 /** 205 * Generates the test function for the operation using the provided writer. 206 * 207 * @param writer writer to write generated code with. 208 */ generateTestFunction(GoWriter writer)209 public void generateTestFunction(GoWriter writer) { 210 writer.addUseImports(SmithyGoDependency.TESTING); 211 writer.openBlock("func " + unitTestFuncNameFormat() + "(t *testing.T) {", "}", unitTestFuncNameArgs(), 212 () -> { 213 skipTests.forEach((skipTest) -> { 214 if (skipTest.matches(service.getId(), operation.getId())) { 215 writer.write("t.Skip(\"disabled test $L $L\")", service.getId(), operation.getId()); 216 writer.write(""); 217 } 218 }); 219 generateTestSetup(writer); 220 221 writer.write("cases := map[string]struct {"); 222 generateTestCaseParams(writer); 223 writer.openBlock("}{", "}", () -> { 224 for (T testCase : testCases) { 225 testCase.getDocumentation().ifPresent(writer::writeDocs); 226 writer.openBlock("$S: {", "},", testCase.getId(), () -> { 227 generateTestCaseValues(writer, testCase); 228 }); 229 } 230 }); 231 232 // And test case iteration/assertions 233 writer.openBlock("for name, c := range cases {", "}", () -> { 234 writer.openBlock("t.Run(name, func(t *testing.T) {", "})", () -> { 235 skipTests.forEach((skipTest) -> { 236 for (T testCase : testCases) { 237 if (skipTest.matches(service.getId(), operation.getId(), testCase.getId())) { 238 writer.openBlock("if name == $S {", "}", testCase.getId(), () -> { 239 writer.write("t.Skip(\"disabled test $L $L\")", service.getId(), 240 operation.getId()); 241 }); 242 writer.write(""); 243 } 244 } 245 }); 246 247 generateTestBodySetup(writer); 248 generateTestServer(writer, "server", this::generateTestServerHandler); 249 generateTestClient(writer, "client"); 250 generateTestInvokeClientOperation(writer, "client"); 251 generateTestAssertions(writer); 252 }); 253 }); 254 }); 255 } 256 257 /** 258 * Writes a single Go structure field key and value. 259 * 260 * @param writer writer to write generated code with. 261 * @param field the field name of the struct member. 262 * @param value the value of the struct member. 263 */ writeStructField(GoWriter writer, String field, Object value)264 protected void writeStructField(GoWriter writer, String field, Object value) { 265 writer.write("$L: $L,", field, value); 266 } 267 268 /** 269 * Writes a single Go structure field key and value. Provides inline formatting of the field value. 270 * 271 * @param writer writer to write generated code with. 272 * @param field the field name of the struct member. 273 * @param valueFormat the format string to use for the field value 274 * @param args the format string arguments for the field value. 275 */ writeStructField(GoWriter writer, String field, String valueFormat, Object... args)276 protected void writeStructField(GoWriter writer, String field, String valueFormat, Object... args) { 277 writer.writeInline("$L: ", field); 278 writer.writeInline(valueFormat, args); 279 writer.write(","); 280 } 281 282 /** 283 * Writes a single Go structure field key and value. Writes the field value inline from the shape and 284 * ObjectNode graph provided. 285 * 286 * @param writer writer to write generated code with. 287 * @param field the field name of the struct member. 288 * @param shape the shape the field member. 289 * @param params the node of values to fill the member with. 290 */ writeStructField(GoWriter writer, String field, StructureShape shape, ObjectNode params)291 protected void writeStructField(GoWriter writer, String field, StructureShape shape, ObjectNode params) { 292 writer.writeInline("$L: ", field); 293 writeShapeValueInline(writer, shape, params); 294 writer.write(","); 295 } 296 297 /** 298 * Writes a single Go structure field key and value. Writes the field value inline from the shape and 299 * ObjectNode graph provided. Value writer is responsible for writing the proceeding comma after the value. 300 * 301 * @param writer writer to write generated code with. 302 * @param field the field name of the struct member. 303 * @param value inline value writer. 304 */ writeStructField(GoWriter writer, String field, Consumer<GoWriter> value)305 protected void writeStructField(GoWriter writer, String field, Consumer<GoWriter> value) { 306 writer.writeInline("$L: ", field); 307 value.accept(writer); 308 } 309 310 /** 311 * Writes a Go structure field for a QueryItem value. 312 * 313 * @param writer writer to write generated code with. 314 * @param field the name of the field. 315 * @param values list of values for the query. 316 */ writeQueryItemsStructField(GoWriter writer, String field, List<String> values)317 protected void writeQueryItemsStructField(GoWriter writer, String field, List<String> values) { 318 writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); 319 writer.openBlock("$L: []smithytesting.QueryItem {", "},", field, () -> { 320 writeQueryItemsValues(writer, values); 321 }); 322 } 323 324 /** 325 * Writes values of query items as slice members. 326 * 327 * @param writer writer to write generated code with. 328 * @param values list of values for the query. 329 */ writeQueryItemsValues(GoWriter writer, List<String> values)330 protected void writeQueryItemsValues(GoWriter writer, List<String> values) { 331 for (String item : values) { 332 String[] parts = item.split("=", 2); 333 String value = ""; 334 if (parts.length > 1) { 335 value = parts[1]; 336 } 337 writer.write("{Key: $S, Value: $S},", parts[0], value); 338 } 339 } 340 341 /** 342 * Writes utility to breakout RawQuery string into its components for testing. 343 * 344 * @param writer writer to write generated code with. 345 * @param source name of variable containing raw query string. 346 * @param target name of destination variable that will be created to hold QueryItems 347 */ writeQueryItemBreakout(GoWriter writer, String source, String target)348 protected void writeQueryItemBreakout(GoWriter writer, String source, String target) { 349 writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); 350 writer.write("$L := smithytesting.ParseRawQuery($L)", target, source); 351 } 352 353 /** 354 * Writes a structure header member with values from a map. 355 * 356 * @param writer writer to write generated code with. 357 * @param field name of the field. 358 * @param values map of header key and value pairs. 359 */ writeHeaderStructField(GoWriter writer, String field, Map<String, String> values)360 protected void writeHeaderStructField(GoWriter writer, String field, Map<String, String> values) { 361 if (values.size() == 0) { 362 return; 363 } 364 writer.openBlock("$L: http.Header{", "},", field, () -> { 365 writeHeaderValues(writer, values); 366 }); 367 } 368 369 /** 370 * Writes individual header key value field pairs. 371 * 372 * @param writer writer to write generated code with. 373 * @param values map of header key/value pairs. 374 */ writeHeaderValues(GoWriter writer, Map<String, String> values)375 protected void writeHeaderValues(GoWriter writer, Map<String, String> values) { 376 values.forEach((k, v) -> { 377 writer.write("$S: []string{$S},", k, v); 378 }); 379 } 380 381 /** 382 * Writes a string slice to a struct field. 383 * 384 * @param writer writer to write generated code with. 385 * @param field the name of the field. 386 * @param values the list of field values. 387 */ writeStringSliceStructField(GoWriter writer, String field, List<String> values)388 protected void writeStringSliceStructField(GoWriter writer, String field, List<String> values) { 389 if (values.size() == 0) { 390 return; 391 } 392 393 writer.openBlock("$L: []string{", "},", field, () -> { 394 writeStringSliceValues(writer, values); 395 }); 396 } 397 398 /** 399 * Writes a list of strings as go string slice members. 400 * 401 * @param writer writer to write generated code with. 402 * @param values the list of string values. 403 */ writeStringSliceValues(GoWriter writer, List<String> values)404 protected void writeStringSliceValues(GoWriter writer, List<String> values) { 405 for (String value : values) { 406 writer.write("$S,", value); 407 } 408 } 409 410 /** 411 * Writes the assertion for comparing two scalar values. 412 * 413 * @param writer writer to write generated code with. 414 * @param expect variable name of the expected value. 415 * @param actual variable name of the actual value. 416 * @param tag additional error message description. 417 */ writeAssertScalarEqual(GoWriter writer, String expect, String actual, String tag)418 protected void writeAssertScalarEqual(GoWriter writer, String expect, String actual, String tag) { 419 writer.openBlock("if e, a := $L, $L; e != a {", "}", expect, actual, () -> { 420 writer.write("t.Errorf(\"expect %v $L, got %v\", e, a)", tag); 421 }); 422 } 423 424 /** 425 * Writes the assertion for comparing two complex type values, e.g. structures. 426 * 427 * @param writer writer to write generated code with. 428 * @param expect the variable name of the expected value. 429 * @param actual the variable name of the actual value. 430 * @param ignoreTypes list of type values that should be ignored by the compare. 431 */ writeAssertComplexEqual( GoWriter writer, String expect, String actual, String[] ignoreTypes )432 protected void writeAssertComplexEqual( 433 GoWriter writer, String expect, String actual, String[] ignoreTypes 434 ) { 435 writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); 436 writer.addUseImports(SmithyGoDependency.GO_CMP_OPTIONS); 437 438 writer.writeInline("if err := smithytesting.CompareValues($L, $L, cmpopts.IgnoreUnexported(", expect, actual); 439 440 for (String ignoreType : ignoreTypes) { 441 writer.write("$L,", ignoreType); 442 } 443 444 writer.writeInline(")); err != nil {"); 445 writer.write(" t.Errorf(\"expect $L value match:\\n%v\", err)", expect); 446 writer.write("}"); 447 } 448 449 /** 450 * Writes assertion that a variable's value must be nil. 451 * 452 * @param writer writer to write generated code with. 453 * @param field the variable name of the value. 454 */ writeAssertNil(GoWriter writer, String field)455 protected void writeAssertNil(GoWriter writer, String field) { 456 writer.openBlock("if $L != nil {", "}", field, () -> { 457 writer.write("t.Fatalf(\"expect nil $L, got %v\", $L)", field, field); 458 }); 459 } 460 461 /** 462 * Writes the assertion that a variable must not be nil. 463 * 464 * @param writer writer to write generated code with. 465 * @param field the variable name of the value. 466 */ writeAssertNotNil(GoWriter writer, String field)467 protected void writeAssertNotNil(GoWriter writer, String field) { 468 writer.openBlock("if $L == nil {", "}", field, () -> { 469 writer.write("t.Fatalf(\"expect not nil $L\")", field); 470 }); 471 } 472 473 /** 474 * Writes the assertion that query contains expected values. 475 * 476 * @param writer writer to write generated code with. 477 * @param expect variable name with the expected values. 478 * @param actual variable name with the actual values. 479 */ writeAssertHasQuery(GoWriter writer, String expect, String actual)480 void writeAssertHasQuery(GoWriter writer, String expect, String actual) { 481 writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); 482 writer.write("smithytesting.AssertHasQuery(t, $L, $L)", expect, actual); 483 } 484 485 /** 486 * Writes the assertion that an query contains keys. 487 * 488 * @param writer writer to write generated code with. 489 * @param expect variable name with the expected values. 490 * @param actual variable name with the actual values. 491 */ writeAssertRequireQuery(GoWriter writer, String expect, String actual)492 protected void writeAssertRequireQuery(GoWriter writer, String expect, String actual) { 493 writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); 494 writer.write("smithytesting.AssertHasQueryKeys(t, $L, $L)", expect, actual); 495 } 496 497 /** 498 * Writes the assertion that an query must not contain keys. 499 * 500 * @param writer writer to write generated code with. 501 * @param expect variable name with the expected values. 502 * @param actual variable name with the actual values. 503 */ writeAssertForbidQuery(GoWriter writer, String expect, String actual)504 protected void writeAssertForbidQuery(GoWriter writer, String expect, String actual) { 505 writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); 506 writer.write("smithytesting.AssertNotHaveQueryKeys(t, $L, $L)", expect, actual); 507 } 508 509 /** 510 * Writes the assertion that headers contain expected values. 511 * 512 * @param writer writer to write generated code with. 513 * @param expect variable name with the expected values. 514 * @param actual variable name with the actual values. 515 */ writeAssertHasHeader(GoWriter writer, String expect, String actual)516 protected void writeAssertHasHeader(GoWriter writer, String expect, String actual) { 517 writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); 518 writer.write("smithytesting.AssertHasHeader(t, $L, $L)", expect, actual); 519 } 520 521 /** 522 * Writes the assertion that the header contains keys. 523 * 524 * @param writer writer to write generated code with. 525 * @param expect variable name with the expected values. 526 * @param actual variable name with the actual values. 527 */ writeAssertRequireHeader(GoWriter writer, String expect, String actual)528 protected void writeAssertRequireHeader(GoWriter writer, String expect, String actual) { 529 writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); 530 writer.write("smithytesting.AssertHasHeaderKeys(t, $L, $L)", expect, actual); 531 } 532 533 /** 534 * Writes the assertion that the header must not contain keys. 535 * 536 * @param writer writer to write generated code with. 537 * @param expect variable name with the expected values. 538 * @param actual variable name with the actual values. 539 */ writeAssertForbidHeader(GoWriter writer, String expect, String actual)540 protected void writeAssertForbidHeader(GoWriter writer, String expect, String actual) { 541 writer.addUseImports(SmithyGoDependency.SMITHY_TESTING); 542 writer.write("smithytesting.AssertNotHaveHeaderKeys(t, $L, $L)", expect, actual); 543 } 544 545 /** 546 * Writes a shape type declaration value filled with values in the ObjectNode. 547 * 548 * @param writer writer to write generated code with. 549 * @param shape shape of the value type to be created. 550 * @param params values to initialize shape type with. 551 */ writeShapeValueInline(GoWriter writer, StructureShape shape, ObjectNode params)552 protected void writeShapeValueInline(GoWriter writer, StructureShape shape, ObjectNode params) { 553 new ShapeValueGenerator(model, symbolProvider, shapeValueGeneratorConfig) 554 .writePointableStructureShapeValueInline(writer, shape, params); 555 } 556 557 /** 558 * Returns if the operation has an idempotency token input member. 559 * 560 * @return if the operation has an idempotency token input member. 561 */ hasIdempotencyTokenInputMember()562 private boolean hasIdempotencyTokenInputMember() { 563 for (MemberShape member : inputShape.members()) { 564 if (member.hasTrait(IdempotencyTokenTrait.class)) { 565 return true; 566 } 567 } 568 return false; 569 } 570 571 public abstract static class Builder<T extends HttpMessageTestCase> { 572 protected Model model; 573 protected SymbolProvider symbolProvider; 574 protected String protocolName = ""; 575 protected ServiceShape service; 576 protected OperationShape operation; 577 protected List<T> testCases = new ArrayList<>(); 578 protected Set<ConfigValue> clientConfigValues = new TreeSet<>(); 579 protected Set<SkipTest> skipTests = new TreeSet<>(); 580 protected ShapeValueGenerator.Config shapeValueGeneratorConfig = ShapeValueGenerator.Config.builder().build(); 581 model(Model model)582 public Builder<T> model(Model model) { 583 this.model = model; 584 return this; 585 } 586 symbolProvider(SymbolProvider symbolProvider)587 public Builder<T> symbolProvider(SymbolProvider symbolProvider) { 588 this.symbolProvider = symbolProvider; 589 return this; 590 } 591 protocolName(String protocolName)592 public Builder<T> protocolName(String protocolName) { 593 this.protocolName = protocolName; 594 return this; 595 } 596 service(ServiceShape service)597 public Builder<T> service(ServiceShape service) { 598 this.service = service; 599 return this; 600 } 601 operation(OperationShape operation)602 public Builder<T> operation(OperationShape operation) { 603 this.operation = operation; 604 return this; 605 } 606 testCases(List<T> testCases)607 public Builder<T> testCases(List<T> testCases) { 608 this.testCases.clear(); 609 return this.addTestCases(testCases); 610 } 611 addTestCases(List<T> testCases)612 public Builder<T> addTestCases(List<T> testCases) { 613 this.testCases.addAll(testCases); 614 return this; 615 } 616 clientConfigValue(ConfigValue configValue)617 public Builder<T> clientConfigValue(ConfigValue configValue) { 618 this.clientConfigValues.add(configValue); 619 return this; 620 } 621 clientConfigValues(Set<ConfigValue> clientConfigValues)622 public Builder<T> clientConfigValues(Set<ConfigValue> clientConfigValues) { 623 this.clientConfigValues.clear(); 624 return this.addClientConfigValues(clientConfigValues); 625 } 626 addClientConfigValues(Set<ConfigValue> clientConfigValues)627 public Builder<T> addClientConfigValues(Set<ConfigValue> clientConfigValues) { 628 this.clientConfigValues.addAll(clientConfigValues); 629 return this; 630 } 631 skipTest(SkipTest skipTest)632 public Builder<T> skipTest(SkipTest skipTest) { 633 this.skipTests.add(skipTest); 634 return this; 635 } 636 skipTests(Set<SkipTest> skipTests)637 public Builder<T> skipTests(Set<SkipTest> skipTests) { 638 this.skipTests.clear(); 639 return this.addSkipTests(skipTests); 640 } 641 addSkipTests(Set<SkipTest> skipTests)642 public Builder<T> addSkipTests(Set<SkipTest> skipTests) { 643 this.skipTests.addAll(skipTests); 644 return this; 645 } 646 shapeValueGeneratorConfig(ShapeValueGenerator.Config config)647 public Builder<T> shapeValueGeneratorConfig(ShapeValueGenerator.Config config) { 648 this.shapeValueGeneratorConfig = config; 649 return this; 650 } 651 build()652 abstract HttpProtocolUnitTestGenerator<T> build(); 653 } 654 655 /** 656 * Represents a test client option configuration value. 657 */ 658 public static class ConfigValue implements Comparable<ConfigValue> { 659 private final String name; 660 private final Consumer<GoWriter> value; 661 ConfigValue(Builder builder)662 ConfigValue(Builder builder) { 663 this.name = SmithyBuilder.requiredState("name", builder.name); 664 this.value = SmithyBuilder.requiredState("value", builder.value); 665 } 666 667 /** 668 * Get the config field name. 669 * 670 * @return the field name 671 */ getName()672 public String getName() { 673 return name; 674 } 675 676 /** 677 * Get the inline value writer for the field. 678 * 679 * @return the inline value writer 680 */ getValue()681 public Consumer<GoWriter> getValue() { 682 return value; 683 } 684 builder()685 public static Builder builder() { 686 return new Builder(); 687 } 688 689 @Override compareTo(ConfigValue o)690 public int compareTo(ConfigValue o) { 691 return getName().compareTo(o.getName()); 692 } 693 694 @Override equals(Object o)695 public boolean equals(Object o) { 696 if (this == o) { 697 return true; 698 } 699 if (o == null || getClass() != o.getClass()) { 700 return false; 701 } 702 ConfigValue that = (ConfigValue) o; 703 return Objects.equals(getName(), that.getName()) 704 && Objects.equals(getValue(), that.getValue()); 705 } 706 707 @Override hashCode()708 public int hashCode() { 709 return Objects.hash(name, value); 710 } 711 712 /** 713 * Builder for {@link ConfigValue}. 714 */ 715 public static final class Builder implements SmithyBuilder<ConfigValue> { 716 private String name; 717 private Consumer<GoWriter> value; 718 Builder()719 private Builder() { 720 } 721 722 /** 723 * Set the name of the field. 724 * 725 * @param name field name 726 * @return the builder 727 */ name(String name)728 public Builder name(String name) { 729 this.name = name; 730 return this; 731 } 732 733 /** 734 * Set the inline value writer. 735 * 736 * @param value the inline value writer 737 * @return the builder 738 */ value(Consumer<GoWriter> value)739 public Builder value(Consumer<GoWriter> value) { 740 this.value = value; 741 return this; 742 } 743 744 @Override build()745 public ConfigValue build() { 746 return new ConfigValue(this); 747 } 748 } 749 } 750 751 /** 752 * Represents a test tests that should be skipped. 753 */ 754 public static class SkipTest implements Comparable<SkipTest> { 755 private final ShapeId service; 756 private final ShapeId operation; 757 private final List<String> testNames; 758 SkipTest(Builder builder)759 SkipTest(Builder builder) { 760 this.service = SmithyBuilder.requiredState("service id", builder.service); 761 this.operation = SmithyBuilder.requiredState("operation id", builder.operation); 762 this.testNames = builder.testNames; 763 } 764 765 /** 766 * Get the service the skip test applies to. 767 * 768 * @return the service id 769 */ getService()770 public ShapeId getService() { 771 return service; 772 } 773 774 /** 775 * Get the operation the skip test applies to. 776 * 777 * @return the operation id 778 */ getOperation()779 public ShapeId getOperation() { 780 return operation; 781 } 782 783 /** 784 * Get the names of the tests the skip test applies to. 785 * 786 * @return the name of the test to skip 787 */ getTestNames()788 public List<String> getTestNames() { 789 return testNames; 790 } 791 792 /** 793 * Returns if the skip test case matches the test being evaluated. If a test name isn't specified in the skip 794 * test only the service and operation are considered for matches. 795 * 796 * @param service id of the service 797 * @param operation id of the operation 798 * @param testName name of the test 799 * @return if the skip test matches 800 */ matches(ShapeId service, ShapeId operation, String testName)801 public boolean matches(ShapeId service, ShapeId operation, String testName) { 802 if (!this.service.equals(service)) { 803 return false; 804 } 805 806 if (!this.operation.equals(operation)) { 807 return false; 808 } 809 810 // SkipTests not for specific test should not match this check. 811 if (this.testNames.isEmpty()) { 812 return false; 813 } 814 815 return this.testNames.contains(testName); 816 } 817 818 /** 819 * Returns if the skip test matches the service and operation, with no individual test defined. If an individual 820 * test name is specified the skip test will not match. 821 * 822 * @param service id of the service 823 * @param operation id of the operation 824 * @return if the skip test matches 825 */ matches(ShapeId service, ShapeId operation)826 public boolean matches(ShapeId service, ShapeId operation) { 827 if (!this.service.equals(service)) { 828 return false; 829 } 830 831 if (!this.operation.equals(operation)) { 832 return false; 833 } 834 835 // SkipTests for specific test should not match this check. 836 return this.testNames.isEmpty(); 837 } 838 builder()839 public static Builder builder() { 840 return new Builder(); 841 } 842 843 @Override compareTo(SkipTest o)844 public int compareTo(SkipTest o) { 845 return Comparator.comparing(SkipTest::getService) 846 .thenComparing(SkipTest::getOperation) 847 .compare(this, o); 848 } 849 850 @Override equals(Object o)851 public boolean equals(Object o) { 852 if (this == o) { 853 return true; 854 } 855 if (o == null || getClass() != o.getClass()) { 856 return false; 857 } 858 SkipTest that = (SkipTest) o; 859 return Objects.equals(getService(), that.getService()) 860 && Objects.equals(getOperation(), that.getOperation()) 861 && Objects.equals(getTestNames(), that.getTestNames()); 862 } 863 864 @Override hashCode()865 public int hashCode() { 866 return Objects.hash(service, operation, testNames); 867 } 868 869 /** 870 * Builder for {@link SkipTest}. 871 */ 872 public static final class Builder implements SmithyBuilder<SkipTest> { 873 private ShapeId service; 874 private ShapeId operation; 875 private List<String> testNames = new ArrayList<>(); 876 Builder()877 private Builder() { 878 } 879 880 /** 881 * Set the service of the test. 882 * 883 * @param service field service 884 * @return the builder 885 */ service(ShapeId service)886 public Builder service(ShapeId service) { 887 this.service = service; 888 return this; 889 } 890 891 /** 892 * Set the operation of the test. 893 * 894 * @param operation is the operation of the test to skip 895 * @return the builder 896 */ operation(ShapeId operation)897 public Builder operation(ShapeId operation) { 898 this.operation = operation; 899 return this; 900 } 901 902 /** 903 * Set the name of the test to skip. 904 * 905 * @param testName is the name of the test to skip 906 * @return the builder 907 */ addTestName(String testName)908 public Builder addTestName(String testName) { 909 this.testNames.add(testName); 910 return this; 911 } 912 913 @Override build()914 public SkipTest build() { 915 return new SkipTest(this); 916 } 917 } 918 } 919 } 920