1 // Copyright 2020 Google LLC 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 com.google.api.generator.gapic.composer.common; 16 17 import com.google.api.gax.core.NoCredentialsProvider; 18 import com.google.api.gax.rpc.ApiClientHeaderProvider; 19 import com.google.api.gax.rpc.ApiStreamObserver; 20 import com.google.api.gax.rpc.BidiStreamingCallable; 21 import com.google.api.gax.rpc.ClientStreamingCallable; 22 import com.google.api.gax.rpc.InvalidArgumentException; 23 import com.google.api.gax.rpc.ServerStreamingCallable; 24 import com.google.api.gax.rpc.StatusCode; 25 import com.google.api.generator.engine.ast.AnnotationNode; 26 import com.google.api.generator.engine.ast.AssignmentExpr; 27 import com.google.api.generator.engine.ast.ClassDefinition; 28 import com.google.api.generator.engine.ast.CommentStatement; 29 import com.google.api.generator.engine.ast.ConcreteReference; 30 import com.google.api.generator.engine.ast.EmptyLineStatement; 31 import com.google.api.generator.engine.ast.Expr; 32 import com.google.api.generator.engine.ast.ExprStatement; 33 import com.google.api.generator.engine.ast.LineComment; 34 import com.google.api.generator.engine.ast.MethodDefinition; 35 import com.google.api.generator.engine.ast.MethodInvocationExpr; 36 import com.google.api.generator.engine.ast.PrimitiveValue; 37 import com.google.api.generator.engine.ast.Reference; 38 import com.google.api.generator.engine.ast.ScopeNode; 39 import com.google.api.generator.engine.ast.Statement; 40 import com.google.api.generator.engine.ast.StringObjectValue; 41 import com.google.api.generator.engine.ast.TryCatchStatement; 42 import com.google.api.generator.engine.ast.TypeNode; 43 import com.google.api.generator.engine.ast.ValueExpr; 44 import com.google.api.generator.engine.ast.VaporReference; 45 import com.google.api.generator.engine.ast.Variable; 46 import com.google.api.generator.engine.ast.VariableExpr; 47 import com.google.api.generator.gapic.composer.defaultvalue.DefaultValueComposer; 48 import com.google.api.generator.gapic.composer.store.TypeStore; 49 import com.google.api.generator.gapic.composer.utils.ClassNames; 50 import com.google.api.generator.gapic.model.Field; 51 import com.google.api.generator.gapic.model.GapicClass; 52 import com.google.api.generator.gapic.model.GapicClass.Kind; 53 import com.google.api.generator.gapic.model.GapicContext; 54 import com.google.api.generator.gapic.model.Message; 55 import com.google.api.generator.gapic.model.Method; 56 import com.google.api.generator.gapic.model.MethodArgument; 57 import com.google.api.generator.gapic.model.ResourceName; 58 import com.google.api.generator.gapic.model.Service; 59 import com.google.api.generator.gapic.utils.JavaStyle; 60 import com.google.common.base.Preconditions; 61 import com.google.common.collect.Lists; 62 import com.google.longrunning.Operation; 63 import com.google.protobuf.AbstractMessage; 64 import com.google.protobuf.Any; 65 import java.io.IOException; 66 import java.util.ArrayList; 67 import java.util.Arrays; 68 import java.util.Collections; 69 import java.util.List; 70 import java.util.Map; 71 import java.util.Optional; 72 import java.util.UUID; 73 import java.util.concurrent.ExecutionException; 74 import java.util.function.Function; 75 import java.util.stream.Collectors; 76 import javax.annotation.Generated; 77 import org.junit.After; 78 import org.junit.AfterClass; 79 import org.junit.Assert; 80 import org.junit.Before; 81 import org.junit.BeforeClass; 82 import org.junit.Test; 83 84 public abstract class AbstractServiceClientTestClassComposer implements ClassComposer { 85 86 protected static final Statement EMPTY_LINE_STATEMENT = EmptyLineStatement.create(); 87 88 protected static final String CLIENT_VAR_NAME = "client"; 89 private static final String MOCK_SERVICE_VAR_NAME_PATTERN = "mock%s"; 90 private static final String PAGED_RESPONSE_TYPE_NAME_PATTERN = "%sPagedResponse"; 91 92 protected static final TypeStore FIXED_TYPESTORE = createStaticTypes(); 93 protected static final AnnotationNode TEST_ANNOTATION = 94 AnnotationNode.withType(FIXED_TYPESTORE.get("Test")); 95 96 private final TransportContext transportContext; 97 AbstractServiceClientTestClassComposer(TransportContext transportContext)98 protected AbstractServiceClientTestClassComposer(TransportContext transportContext) { 99 this.transportContext = transportContext; 100 } 101 getTransportContext()102 public TransportContext getTransportContext() { 103 return transportContext; 104 } 105 106 @Override generate(GapicContext context, Service service)107 public GapicClass generate(GapicContext context, Service service) { 108 return generate(ClassNames.getServiceClientTestClassName(service), context, service); 109 } 110 generate(String className, GapicContext context, Service service)111 protected GapicClass generate(String className, GapicContext context, Service service) { 112 // Do not generate Client Test code for Transport if there are no matching RPCs for a Transport 113 if (!service.hasAnyEnabledMethodsForTransport(getTransportContext().transport())) { 114 return GapicClass.createNonGeneratedGapicClass(); 115 } 116 117 Map<String, ResourceName> resourceNames = context.helperResourceNames(); 118 String pakkage = service.pakkage(); 119 TypeStore typeStore = new TypeStore(); 120 addDynamicTypes(context, service, typeStore); 121 GapicClass.Kind kind = Kind.MAIN; 122 123 Map<String, VariableExpr> classMemberVarExprs = 124 createClassMemberVarExprs(service, context, typeStore); 125 126 ClassDefinition classDef = 127 ClassDefinition.builder() 128 .setPackageString(pakkage) 129 .setAnnotations(createClassAnnotations()) 130 .setScope(ScopeNode.PUBLIC) 131 .setName(className) 132 .setStatements(createClassMemberFieldDecls(classMemberVarExprs)) 133 .setMethods( 134 createClassMethods(service, context, classMemberVarExprs, typeStore, resourceNames)) 135 .build(); 136 return GapicClass.create(kind, classDef); 137 } 138 createClassAnnotations()139 private List<AnnotationNode> createClassAnnotations() { 140 return Arrays.asList( 141 AnnotationNode.builder() 142 .setType(FIXED_TYPESTORE.get("Generated")) 143 .setDescription("by gapic-generator-java") 144 .build()); 145 } 146 createClassMemberVarExprs( Service service, GapicContext context, TypeStore typeStore)147 protected abstract Map<String, VariableExpr> createClassMemberVarExprs( 148 Service service, GapicContext context, TypeStore typeStore); 149 createClassMemberFieldDecls( Map<String, VariableExpr> classMemberVarExprs)150 protected List<Statement> createClassMemberFieldDecls( 151 Map<String, VariableExpr> classMemberVarExprs) { 152 Function<VariableExpr, Boolean> isMockVarExprFn = 153 v -> v.type().reference().name().startsWith("Mock"); 154 155 // Ordering matters for pretty-printing and ensuring that test output is deterministic. 156 List<Statement> fieldDeclStatements = new ArrayList<>(); 157 158 // Static fields go first. 159 fieldDeclStatements.addAll( 160 classMemberVarExprs.values().stream() 161 .filter(v -> isMockVarExprFn.apply(v)) 162 .map( 163 v -> 164 ExprStatement.withExpr( 165 v.toBuilder() 166 .setIsDecl(true) 167 .setScope(ScopeNode.PRIVATE) 168 .setIsStatic(true) 169 .build())) 170 .collect(Collectors.toList())); 171 172 fieldDeclStatements.addAll( 173 classMemberVarExprs.values().stream() 174 .filter(v -> !isMockVarExprFn.apply(v)) 175 .map( 176 v -> 177 ExprStatement.withExpr( 178 v.toBuilder().setIsDecl(true).setScope(ScopeNode.PRIVATE).build())) 179 .collect(Collectors.toList())); 180 return fieldDeclStatements; 181 } 182 createClassMethods( Service service, GapicContext context, Map<String, VariableExpr> classMemberVarExprs, TypeStore typeStore, Map<String, ResourceName> resourceNames)183 private List<MethodDefinition> createClassMethods( 184 Service service, 185 GapicContext context, 186 Map<String, VariableExpr> classMemberVarExprs, 187 TypeStore typeStore, 188 Map<String, ResourceName> resourceNames) { 189 List<MethodDefinition> javaMethods = new ArrayList<>(); 190 javaMethods.addAll(createTestAdminMethods(service, context, classMemberVarExprs, typeStore)); 191 javaMethods.addAll(createTestMethods(service, context, classMemberVarExprs, resourceNames)); 192 return javaMethods; 193 } 194 createTestAdminMethods( Service service, GapicContext context, Map<String, VariableExpr> classMemberVarExprs, TypeStore typeStore)195 private List<MethodDefinition> createTestAdminMethods( 196 Service service, 197 GapicContext context, 198 Map<String, VariableExpr> classMemberVarExprs, 199 TypeStore typeStore) { 200 List<MethodDefinition> javaMethods = new ArrayList<>(); 201 javaMethods.add( 202 createStartStaticServerMethod( 203 service, context, classMemberVarExprs, typeStore, "newBuilder")); 204 javaMethods.add(createStopServerMethod(service, classMemberVarExprs)); 205 javaMethods.add(createSetUpMethod(service, classMemberVarExprs, typeStore)); 206 javaMethods.add(createTearDownMethod(service, classMemberVarExprs)); 207 return javaMethods; 208 } 209 createStartStaticServerMethod( Service service, GapicContext context, Map<String, VariableExpr> classMemberVarExprs, TypeStore typeStore, String newBuilderMethod)210 protected abstract MethodDefinition createStartStaticServerMethod( 211 Service service, 212 GapicContext context, 213 Map<String, VariableExpr> classMemberVarExprs, 214 TypeStore typeStore, 215 String newBuilderMethod); 216 createStopServerMethod( Service service, Map<String, VariableExpr> classMemberVarExprs)217 protected abstract MethodDefinition createStopServerMethod( 218 Service service, Map<String, VariableExpr> classMemberVarExprs); 219 createSetUpMethod( Service service, Map<String, VariableExpr> classMemberVarExprs, TypeStore typeStore)220 protected abstract MethodDefinition createSetUpMethod( 221 Service service, Map<String, VariableExpr> classMemberVarExprs, TypeStore typeStore); 222 createTearDownMethod( Service service, Map<String, VariableExpr> classMemberVarExprs)223 protected abstract MethodDefinition createTearDownMethod( 224 Service service, Map<String, VariableExpr> classMemberVarExprs); 225 createTestMethods( Service service, GapicContext context, Map<String, VariableExpr> classMemberVarExprs, Map<String, ResourceName> resourceNames)226 private List<MethodDefinition> createTestMethods( 227 Service service, 228 GapicContext context, 229 Map<String, VariableExpr> classMemberVarExprs, 230 Map<String, ResourceName> resourceNames) { 231 Map<String, Message> messageTypes = context.messages(); 232 List<MethodDefinition> javaMethods = new ArrayList<>(); 233 for (Method method : service.methods()) { 234 if (!method.isSupportedByTransport(getTransportContext().transport())) { 235 javaMethods.add(createUnsupportedTestMethod(method)); 236 continue; 237 } 238 Service matchingService = service; 239 if (method.isMixin()) { 240 int dotIndex = method.mixedInApiName().lastIndexOf("."); 241 String mixinServiceName = method.mixedInApiName().substring(dotIndex + 1); 242 String mixinServiceProtoPackage = method.mixedInApiName().substring(0, dotIndex); 243 Optional<Service> mixinServiceOpt = 244 context.mixinServices().stream() 245 .filter( 246 s -> 247 s.name().equals(mixinServiceName) 248 && s.protoPakkage().equals(mixinServiceProtoPackage)) 249 .findFirst(); 250 if (mixinServiceOpt.isPresent()) { 251 matchingService = mixinServiceOpt.get(); 252 } 253 } 254 255 // Ignore variants for streaming methods as well. 256 if (method.methodSignatures().isEmpty() || !method.stream().equals(Method.Stream.NONE)) { 257 javaMethods.add( 258 createRpcTestMethod( 259 method, 260 service, 261 matchingService, 262 Collections.emptyList(), 263 0, 264 true, 265 classMemberVarExprs, 266 resourceNames, 267 messageTypes)); 268 javaMethods.add( 269 createRpcExceptionTestMethod( 270 method, 271 matchingService, 272 Collections.emptyList(), 273 0, 274 classMemberVarExprs, 275 resourceNames, 276 messageTypes)); 277 } else { 278 for (int i = 0; i < method.methodSignatures().size(); i++) { 279 javaMethods.add( 280 createRpcTestMethod( 281 method, 282 service, 283 matchingService, 284 method.methodSignatures().get(i), 285 i, 286 false, 287 classMemberVarExprs, 288 resourceNames, 289 messageTypes)); 290 javaMethods.add( 291 createRpcExceptionTestMethod( 292 method, 293 matchingService, 294 method.methodSignatures().get(i), 295 i, 296 classMemberVarExprs, 297 resourceNames, 298 messageTypes)); 299 } 300 } 301 } 302 return javaMethods; 303 } 304 305 /** 306 * Creates a test method for a given RPC, e.g. createAssetTest. 307 * 308 * @param method the RPC for which this test method is created. 309 * @param apiService the host service under test. 310 * @param rpcService the service that {@code method} belongs to. This is not equal to {@code 311 * apiService} only when {@code method} is a mixin, in which case {@code rpcService} is the 312 * mixed-in service. If {@code apiService} and {@code rpcService} are different, they will be 313 * used only for pagination. Otherwise, {@code rpcService} subsumes {@code apiService}. 314 * @param methodSignature the method signature of the RPC under test. 315 * @param variantIndex the nth variant of the RPC under test. This applies when we have 316 * polymorphism due to the presence of several method signature annotations in the proto. 317 * @param isRequestArg whether the RPC variant under test take only the request proto message. 318 * @param classMemberVarExprs the class members in the generated test class. 319 * @param resourceNames the resource names available for use. 320 * @param messageTypes the proto message types available for use. 321 */ createRpcTestMethod( Method method, Service apiService, Service rpcService, List<MethodArgument> methodSignature, int variantIndex, boolean isRequestArg, Map<String, VariableExpr> classMemberVarExprs, Map<String, ResourceName> resourceNames, Map<String, Message> messageTypes)322 private MethodDefinition createRpcTestMethod( 323 Method method, 324 Service apiService, 325 Service rpcService, 326 List<MethodArgument> methodSignature, 327 int variantIndex, 328 boolean isRequestArg, 329 Map<String, VariableExpr> classMemberVarExprs, 330 Map<String, ResourceName> resourceNames, 331 Map<String, Message> messageTypes) { 332 if (!method.stream().equals(Method.Stream.NONE)) { 333 return createStreamingRpcTestMethod( 334 rpcService, method, classMemberVarExprs, resourceNames, messageTypes); 335 } 336 // Construct the expected response. 337 TypeNode methodOutputType = method.hasLro() ? method.lro().responseType() : method.outputType(); 338 List<Expr> methodExprs = new ArrayList<>(); 339 340 TypeNode repeatedResponseType = null; 341 VariableExpr responsesElementVarExpr = null; 342 String mockServiceVarName = getMockServiceVarName(rpcService); 343 if (method.isPaged()) { 344 Message methodOutputMessage = messageTypes.get(method.outputType().reference().fullName()); 345 Field repeatedPagedResultsField = methodOutputMessage.findAndUnwrapPaginatedRepeatedField(); 346 Preconditions.checkNotNull( 347 repeatedPagedResultsField, 348 String.format( 349 "No repeated field found for paged method %s with output message type %s", 350 method.name(), methodOutputMessage.name())); 351 352 if (repeatedPagedResultsField.isMap()) { 353 repeatedResponseType = 354 TypeNode.withReference(repeatedPagedResultsField.type().reference().generics().get(1)); 355 } else { 356 // Must be a non-repeated type. 357 repeatedResponseType = repeatedPagedResultsField.type(); 358 } 359 360 responsesElementVarExpr = 361 VariableExpr.withVariable( 362 Variable.builder().setType(repeatedResponseType).setName("responsesElement").build()); 363 methodExprs.add( 364 AssignmentExpr.builder() 365 .setVariableExpr(responsesElementVarExpr.toBuilder().setIsDecl(true).build()) 366 .setValueExpr( 367 DefaultValueComposer.createValue( 368 Field.builder() 369 .setType(repeatedResponseType) 370 .setName("responsesElement") 371 .setIsMessage(!repeatedResponseType.isProtoPrimitiveType()) 372 .build())) 373 .build()); 374 } 375 376 VariableExpr expectedResponseVarExpr = 377 VariableExpr.withVariable( 378 Variable.builder().setType(methodOutputType).setName("expectedResponse").build()); 379 Expr expectedResponseValExpr = null; 380 if (method.isPaged()) { 381 Message methodOutputMessage = messageTypes.get(method.outputType().reference().fullName()); 382 Field firstRepeatedField = methodOutputMessage.findAndUnwrapPaginatedRepeatedField(); 383 Preconditions.checkNotNull( 384 firstRepeatedField, 385 String.format( 386 "Expected paged RPC %s to have a repeated field in the response %s but found none", 387 method.name(), methodOutputMessage.name())); 388 389 expectedResponseValExpr = 390 DefaultValueComposer.createSimplePagedResponseValue( 391 method.outputType(), 392 firstRepeatedField.name(), 393 responsesElementVarExpr, 394 firstRepeatedField.isMap()); 395 } else { 396 if (messageTypes.containsKey(methodOutputType.reference().fullName())) { 397 expectedResponseValExpr = 398 DefaultValueComposer.createSimpleMessageBuilderValue( 399 messageTypes.get(methodOutputType.reference().fullName()), 400 resourceNames, 401 messageTypes, 402 method.httpBindings()); 403 } else { 404 // Wrap this in a field so we don't have to split the helper into lots of different methods, 405 // or duplicate it for VariableExpr. 406 expectedResponseValExpr = 407 DefaultValueComposer.createValue( 408 Field.builder() 409 .setType(methodOutputType) 410 .setIsMessage(true) 411 .setName("expectedResponse") 412 .build()); 413 } 414 } 415 416 methodExprs.add( 417 AssignmentExpr.builder() 418 .setVariableExpr(expectedResponseVarExpr.toBuilder().setIsDecl(true).build()) 419 .setValueExpr(expectedResponseValExpr) 420 .build()); 421 422 if (method.hasLro() 423 && (method.lro().operationServiceStubType() == null 424 || !method.lro().responseType().equals(method.outputType()))) { 425 426 VariableExpr resultOperationVarExpr = 427 VariableExpr.withVariable( 428 Variable.builder() 429 .setType(FIXED_TYPESTORE.get("Operation")) 430 .setName("resultOperation") 431 .build()); 432 methodExprs.add( 433 AssignmentExpr.builder() 434 .setVariableExpr(resultOperationVarExpr.toBuilder().setIsDecl(true).build()) 435 .setValueExpr( 436 DefaultValueComposer.createSimpleOperationBuilderValue( 437 String.format("%sTest", JavaStyle.toLowerCamelCase(method.name())), 438 expectedResponseVarExpr)) 439 .build()); 440 methodExprs.add( 441 MethodInvocationExpr.builder() 442 .setExprReferenceExpr(classMemberVarExprs.get(mockServiceVarName)) 443 .setMethodName("addResponse") 444 .setArguments(resultOperationVarExpr) 445 .build()); 446 } else { 447 methodExprs.add( 448 MethodInvocationExpr.builder() 449 .setExprReferenceExpr(classMemberVarExprs.get(mockServiceVarName)) 450 .setMethodName("addResponse") 451 .setArguments(expectedResponseVarExpr) 452 .build()); 453 } 454 List<Statement> methodStatements = new ArrayList<>(); 455 methodStatements.addAll( 456 methodExprs.stream().map(e -> ExprStatement.withExpr(e)).collect(Collectors.toList())); 457 methodExprs.clear(); 458 methodStatements.add(EMPTY_LINE_STATEMENT); 459 460 // Construct the request or method arguments. 461 VariableExpr requestVarExpr = null; 462 Message requestMessage = null; 463 List<VariableExpr> argExprs = new ArrayList<>(); 464 if (isRequestArg) { 465 requestVarExpr = 466 VariableExpr.withVariable( 467 Variable.builder().setType(method.inputType()).setName("request").build()); 468 argExprs.add(requestVarExpr); 469 requestMessage = messageTypes.get(method.inputType().reference().fullName()); 470 Preconditions.checkNotNull(requestMessage); 471 Map<String, String> pathParamValuePatterns = Collections.emptyMap(); 472 if (getTransportContext().useValuePatterns() && method.hasHttpBindings()) { 473 pathParamValuePatterns = method.httpBindings().getPathParametersValuePatterns(); 474 } 475 476 Expr valExpr = 477 DefaultValueComposer.createSimpleMessageBuilderValue( 478 requestMessage, 479 resourceNames, 480 messageTypes, 481 pathParamValuePatterns, 482 method.httpBindings()); 483 methodExprs.add( 484 AssignmentExpr.builder() 485 .setVariableExpr(requestVarExpr.toBuilder().setIsDecl(true).build()) 486 .setValueExpr(valExpr) 487 .build()); 488 } else { 489 Map<String, String> valuePatterns = Collections.emptyMap(); 490 if (getTransportContext().useValuePatterns() && method.hasHttpBindings()) { 491 valuePatterns = method.httpBindings().getPathParametersValuePatterns(); 492 } 493 for (MethodArgument methodArg : methodSignature) { 494 String methodArgName = JavaStyle.toLowerCamelCase(methodArg.name()); 495 VariableExpr varExpr = 496 VariableExpr.withVariable( 497 Variable.builder().setType(methodArg.type()).setName(methodArgName).build()); 498 argExprs.add(varExpr); 499 Expr valExpr = 500 DefaultValueComposer.createMethodArgValue( 501 methodArg, resourceNames, messageTypes, valuePatterns, method.httpBindings()); 502 methodExprs.add( 503 AssignmentExpr.builder() 504 .setVariableExpr(varExpr.toBuilder().setIsDecl(true).build()) 505 .setValueExpr(valExpr) 506 .build()); 507 } 508 } 509 methodStatements.addAll( 510 methodExprs.stream().map(e -> ExprStatement.withExpr(e)).collect(Collectors.toList())); 511 methodExprs.clear(); 512 methodStatements.add(EMPTY_LINE_STATEMENT); 513 514 // Call the RPC Java method. 515 VariableExpr actualResponseVarExpr = 516 VariableExpr.withVariable( 517 Variable.builder() 518 .setType( 519 !method.isPaged() 520 ? methodOutputType 521 // If this method is a paginated mixin, use the host service, since 522 // ServiceClient defines the paged response class and the mixed-in service 523 // does not have a client. 524 : getPagedResponseType(method, method.isMixin() ? apiService : rpcService)) 525 .setName(method.isPaged() ? "pagedListResponse" : "actualResponse") 526 .build()); 527 Expr rpcJavaMethodInvocationExpr = 528 MethodInvocationExpr.builder() 529 .setExprReferenceExpr(classMemberVarExprs.get("client")) 530 .setMethodName( 531 JavaStyle.toLowerCamelCase(method.name()) + (method.hasLro() ? "Async" : "")) 532 .setArguments(argExprs.stream().map(e -> (Expr) e).collect(Collectors.toList())) 533 .setReturnType(actualResponseVarExpr.type()) 534 .build(); 535 if (method.hasLro()) { 536 rpcJavaMethodInvocationExpr = 537 MethodInvocationExpr.builder() 538 .setExprReferenceExpr(rpcJavaMethodInvocationExpr) 539 .setMethodName("get") 540 .setReturnType(rpcJavaMethodInvocationExpr.type()) 541 .build(); 542 } 543 544 boolean returnsVoid = isProtoEmptyType(methodOutputType); 545 if (returnsVoid) { 546 methodExprs.add(rpcJavaMethodInvocationExpr); 547 } else { 548 methodExprs.add( 549 AssignmentExpr.builder() 550 .setVariableExpr(actualResponseVarExpr.toBuilder().setIsDecl(true).build()) 551 .setValueExpr(rpcJavaMethodInvocationExpr) 552 .build()); 553 } 554 555 if (method.isPaged()) { 556 Message methodOutputMessage = messageTypes.get(method.outputType().reference().fullName()); 557 Field repeatedPagedResultsField = methodOutputMessage.findAndUnwrapPaginatedRepeatedField(); 558 559 // Assign the resources variable. 560 VariableExpr resourcesVarExpr = 561 VariableExpr.withVariable( 562 Variable.builder() 563 .setType( 564 TypeNode.withReference( 565 ConcreteReference.builder() 566 .setClazz(List.class) 567 .setGenerics( 568 Arrays.asList(repeatedPagedResultsField.type().reference())) 569 .build())) 570 .setName("resources") 571 .build()); 572 Expr iterateAllExpr = 573 MethodInvocationExpr.builder() 574 .setExprReferenceExpr(actualResponseVarExpr) 575 .setMethodName("iterateAll") 576 .build(); 577 Expr resourcesValExpr = 578 MethodInvocationExpr.builder() 579 .setStaticReferenceType(FIXED_TYPESTORE.get("Lists")) 580 .setMethodName("newArrayList") 581 .setArguments(iterateAllExpr) 582 .setReturnType(resourcesVarExpr.type()) 583 .build(); 584 585 methodStatements.addAll( 586 methodExprs.stream().map(e -> ExprStatement.withExpr(e)).collect(Collectors.toList())); 587 methodExprs.clear(); 588 methodStatements.add(EMPTY_LINE_STATEMENT); 589 590 methodStatements.add( 591 ExprStatement.withExpr( 592 AssignmentExpr.builder() 593 .setVariableExpr(resourcesVarExpr.toBuilder().setIsDecl(true).build()) 594 .setValueExpr(resourcesValExpr) 595 .build())); 596 methodStatements.add(EMPTY_LINE_STATEMENT); 597 598 // Assert the size is equivalent. 599 methodExprs.add( 600 MethodInvocationExpr.builder() 601 .setStaticReferenceType(FIXED_TYPESTORE.get("Assert")) 602 .setMethodName("assertEquals") 603 .setArguments( 604 ValueExpr.withValue( 605 PrimitiveValue.builder().setType(TypeNode.INT).setValue("1").build()), 606 MethodInvocationExpr.builder() 607 .setExprReferenceExpr(resourcesVarExpr) 608 .setMethodName("size") 609 .setReturnType(TypeNode.INT) 610 .build()) 611 .build()); 612 613 // Assert the responses are equivalent. 614 Preconditions.checkNotNull( 615 repeatedPagedResultsField, 616 String.format( 617 "No repeated field found for paged method %s with output message type %s", 618 method.name(), methodOutputMessage.name())); 619 620 Expr zeroExpr = 621 ValueExpr.withValue(PrimitiveValue.builder().setType(TypeNode.INT).setValue("0").build()); 622 623 // Generated code: 624 // Assert.assertEquals( 625 // expectedResponse.getItemsMap().entrySet().iterator().next(), resources.get(0)); 626 // ) 627 Expr expectedPagedResponseExpr; 628 if (repeatedPagedResultsField.isMap()) { 629 expectedPagedResponseExpr = 630 MethodInvocationExpr.builder() 631 .setMethodName("next") 632 .setExprReferenceExpr( 633 MethodInvocationExpr.builder() 634 .setMethodName("iterator") 635 .setExprReferenceExpr( 636 MethodInvocationExpr.builder() 637 .setMethodName("entrySet") 638 .setExprReferenceExpr( 639 MethodInvocationExpr.builder() 640 .setExprReferenceExpr(expectedResponseVarExpr) 641 .setMethodName( 642 String.format( 643 "get%sMap", 644 JavaStyle.toUpperCamelCase( 645 repeatedPagedResultsField.name()))) 646 .build()) 647 .build()) 648 .build()) 649 .build(); 650 651 } else { 652 // Generated code: 653 // Assert.assertEquals(expectedResponse.getItemsList().get(0), resources.get(0)); 654 expectedPagedResponseExpr = 655 MethodInvocationExpr.builder() 656 .setExprReferenceExpr(expectedResponseVarExpr) 657 .setMethodName( 658 String.format( 659 "get%sList", JavaStyle.toUpperCamelCase(repeatedPagedResultsField.name()))) 660 .build(); 661 expectedPagedResponseExpr = 662 MethodInvocationExpr.builder() 663 .setExprReferenceExpr(expectedPagedResponseExpr) 664 .setMethodName("get") 665 .setArguments(zeroExpr) 666 .build(); 667 } 668 Expr actualPagedResponseExpr = 669 MethodInvocationExpr.builder() 670 .setExprReferenceExpr(resourcesVarExpr) 671 .setMethodName("get") 672 .setArguments(zeroExpr) 673 .build(); 674 675 methodExprs.add( 676 MethodInvocationExpr.builder() 677 .setStaticReferenceType(FIXED_TYPESTORE.get("Assert")) 678 .setMethodName("assertEquals") 679 .setArguments(expectedPagedResponseExpr, actualPagedResponseExpr) 680 .build()); 681 } else if (!returnsVoid) { 682 methodExprs.add( 683 MethodInvocationExpr.builder() 684 .setStaticReferenceType(FIXED_TYPESTORE.get("Assert")) 685 .setMethodName("assertEquals") 686 .setArguments(expectedResponseVarExpr, actualResponseVarExpr) 687 .build()); 688 } 689 methodStatements.addAll( 690 methodExprs.stream().map(e -> ExprStatement.withExpr(e)).collect(Collectors.toList())); 691 methodExprs.clear(); 692 methodStatements.add(EMPTY_LINE_STATEMENT); 693 694 methodStatements.addAll( 695 constructRpcTestCheckerLogic( 696 method, 697 methodSignature, 698 rpcService, 699 isRequestArg, 700 classMemberVarExprs, 701 requestVarExpr, 702 requestMessage)); 703 704 String testMethodName = 705 String.format( 706 "%sTest%s", 707 JavaStyle.toLowerCamelCase(method.name()), variantIndex > 0 ? variantIndex + 1 : ""); 708 709 return MethodDefinition.builder() 710 .setAnnotations(Arrays.asList(TEST_ANNOTATION)) 711 .setScope(ScopeNode.PUBLIC) 712 .setReturnType(TypeNode.VOID) 713 .setName(testMethodName) 714 .setThrowsExceptions(Arrays.asList(TypeNode.withExceptionClazz(Exception.class))) 715 .setBody(methodStatements) 716 .build(); 717 } 718 constructRpcTestCheckerLogic( Method method, List<MethodArgument> methodSignature, Service service, boolean isRequestArg, Map<String, VariableExpr> classMemberVarExprs, VariableExpr requestVarExpr, Message requestMessage)719 protected abstract List<Statement> constructRpcTestCheckerLogic( 720 Method method, 721 List<MethodArgument> methodSignature, 722 Service service, 723 boolean isRequestArg, 724 Map<String, VariableExpr> classMemberVarExprs, 725 VariableExpr requestVarExpr, 726 Message requestMessage); 727 createStreamingRpcTestMethod( Service service, Method method, Map<String, VariableExpr> classMemberVarExprs, Map<String, ResourceName> resourceNames, Map<String, Message> messageTypes)728 protected abstract MethodDefinition createStreamingRpcTestMethod( 729 Service service, 730 Method method, 731 Map<String, VariableExpr> classMemberVarExprs, 732 Map<String, ResourceName> resourceNames, 733 Map<String, Message> messageTypes); 734 735 /** 736 * Creates a test method to exercise exceptions for a given RPC, e.g. createAssetTest. 737 * 738 * @param method the RPC for which this test method is created. 739 * @param service the service that {@code method} belongs to. 740 * @param methodSignature the method signature of the RPC under test. 741 * @param variantIndex the nth variant of the RPC under test. This applies when we have 742 * polymorphism due to the presence of several method signature annotations in the proto. 743 * @param classMemberVarExprs the class members in the generated test class. 744 * @param resourceNames the resource names available for use. 745 * @param messageTypes the proto message types available for use. 746 */ createRpcExceptionTestMethod( Method method, Service service, List<MethodArgument> methodSignature, int variantIndex, Map<String, VariableExpr> classMemberVarExprs, Map<String, ResourceName> resourceNames, Map<String, Message> messageTypes)747 protected abstract MethodDefinition createRpcExceptionTestMethod( 748 Method method, 749 Service service, 750 List<MethodArgument> methodSignature, 751 int variantIndex, 752 Map<String, VariableExpr> classMemberVarExprs, 753 Map<String, ResourceName> resourceNames, 754 Map<String, Message> messageTypes); 755 createStreamingRpcExceptionTestStatements( Method method, Map<String, VariableExpr> classMemberVarExprs, Map<String, ResourceName> resourceNames, Map<String, Message> messageTypes)756 protected abstract List<Statement> createStreamingRpcExceptionTestStatements( 757 Method method, 758 Map<String, VariableExpr> classMemberVarExprs, 759 Map<String, ResourceName> resourceNames, 760 Map<String, Message> messageTypes); 761 createUnsupportedTestMethod(Method method)762 protected MethodDefinition createUnsupportedTestMethod(Method method) { 763 String javaMethodName = JavaStyle.toLowerCamelCase(method.name()); 764 String exceptionTestMethodName = String.format("%sUnsupportedMethodTest", javaMethodName); 765 766 List<Statement> methodBody = 767 Collections.singletonList( 768 CommentStatement.withComment( 769 LineComment.withComment( 770 "The " 771 + javaMethodName 772 + "() method is not supported in " 773 + String.join("+", getTransportContext().transportNames()) 774 + " transport.\n" 775 + "This empty test is generated for technical reasons."))); 776 777 return MethodDefinition.builder() 778 .setAnnotations(Arrays.asList(TEST_ANNOTATION)) 779 .setScope(ScopeNode.PUBLIC) 780 .setReturnType(TypeNode.VOID) 781 .setName(exceptionTestMethodName) 782 .setThrowsExceptions(Arrays.asList(TypeNode.withExceptionClazz(Exception.class))) 783 .setBody(methodBody) 784 .build(); 785 } 786 createRpcExceptionTestStatements( Method method, List<MethodArgument> methodSignature, Map<String, VariableExpr> classMemberVarExprs, Map<String, ResourceName> resourceNames, Map<String, Message> messageTypes)787 protected List<Statement> createRpcExceptionTestStatements( 788 Method method, 789 List<MethodArgument> methodSignature, 790 Map<String, VariableExpr> classMemberVarExprs, 791 Map<String, ResourceName> resourceNames, 792 Map<String, Message> messageTypes) { 793 List<VariableExpr> argVarExprs = new ArrayList<>(); 794 List<Expr> tryBodyExprs = new ArrayList<>(); 795 796 if (methodSignature.isEmpty()) { 797 // Construct the actual request. 798 VariableExpr varExpr = 799 VariableExpr.withVariable( 800 Variable.builder().setType(method.inputType()).setName("request").build()); 801 argVarExprs.add(varExpr); 802 Message requestMessage = messageTypes.get(method.inputType().reference().fullName()); 803 Preconditions.checkNotNull(requestMessage); 804 Map<String, String> valuePatterns = Collections.emptyMap(); 805 if (getTransportContext().useValuePatterns() && method.hasHttpBindings()) { 806 valuePatterns = method.httpBindings().getPathParametersValuePatterns(); 807 } 808 Expr valExpr = 809 DefaultValueComposer.createSimpleMessageBuilderValue( 810 requestMessage, resourceNames, messageTypes, valuePatterns, method.httpBindings()); 811 tryBodyExprs.add( 812 AssignmentExpr.builder() 813 .setVariableExpr(varExpr.toBuilder().setIsDecl(true).build()) 814 .setValueExpr(valExpr) 815 .build()); 816 } else { 817 Map<String, String> valuePatterns = Collections.emptyMap(); 818 if (getTransportContext().useValuePatterns() && method.hasHttpBindings()) { 819 valuePatterns = method.httpBindings().getPathParametersValuePatterns(); 820 } 821 for (MethodArgument methodArg : methodSignature) { 822 String methodArgName = JavaStyle.toLowerCamelCase(methodArg.name()); 823 VariableExpr varExpr = 824 VariableExpr.withVariable( 825 Variable.builder().setType(methodArg.type()).setName(methodArgName).build()); 826 argVarExprs.add(varExpr); 827 Expr valExpr = 828 DefaultValueComposer.createMethodArgValue( 829 methodArg, resourceNames, messageTypes, valuePatterns, method.httpBindings()); 830 tryBodyExprs.add( 831 AssignmentExpr.builder() 832 .setVariableExpr(varExpr.toBuilder().setIsDecl(true).build()) 833 .setValueExpr(valExpr) 834 .build()); 835 } 836 } 837 String rpcJavaName = JavaStyle.toLowerCamelCase(method.name()); 838 if (method.hasLro()) { 839 rpcJavaName += "Async"; 840 } 841 MethodInvocationExpr rpcJavaMethodInvocationExpr = 842 MethodInvocationExpr.builder() 843 .setExprReferenceExpr(classMemberVarExprs.get("client")) 844 .setMethodName(rpcJavaName) 845 .setArguments(argVarExprs.stream().map(e -> (Expr) e).collect(Collectors.toList())) 846 .build(); 847 if (method.hasLro()) { 848 rpcJavaMethodInvocationExpr = 849 MethodInvocationExpr.builder() 850 .setExprReferenceExpr(rpcJavaMethodInvocationExpr) 851 .setMethodName("get") 852 .build(); 853 } 854 tryBodyExprs.add(rpcJavaMethodInvocationExpr); 855 856 VariableExpr catchExceptionVarExpr = 857 VariableExpr.builder() 858 .setVariable( 859 Variable.builder() 860 .setType( 861 TypeNode.withExceptionClazz( 862 method.hasLro() 863 ? ExecutionException.class 864 : InvalidArgumentException.class)) 865 .setName("e") 866 .build()) 867 .build(); 868 869 List<Statement> catchBody = 870 method.hasLro() 871 ? createRpcLroExceptionTestCatchBody(catchExceptionVarExpr, false) 872 : Arrays.asList( 873 CommentStatement.withComment(LineComment.withComment("Expected exception."))); 874 // Assert a failure if no exception was raised. 875 tryBodyExprs.add( 876 MethodInvocationExpr.builder() 877 .setStaticReferenceType(FIXED_TYPESTORE.get("Assert")) 878 .setMethodName("fail") 879 .setArguments(ValueExpr.withValue(StringObjectValue.withValue("No exception raised"))) 880 .build()); 881 882 TryCatchStatement tryCatchBlock = 883 TryCatchStatement.builder() 884 .setTryBody( 885 tryBodyExprs.stream() 886 .map(e -> ExprStatement.withExpr(e)) 887 .collect(Collectors.toList())) 888 .addCatch(catchExceptionVarExpr.toBuilder().setIsDecl(true).build(), catchBody) 889 .build(); 890 891 return Arrays.asList(EMPTY_LINE_STATEMENT, tryCatchBlock); 892 } 893 createRpcLroExceptionTestCatchBody( VariableExpr exceptionExpr, boolean isStreaming)894 protected abstract List<Statement> createRpcLroExceptionTestCatchBody( 895 VariableExpr exceptionExpr, boolean isStreaming); 896 897 /* ========================================= 898 * Type creator methods. 899 * ========================================= 900 */ 901 createStaticTypes()902 private static TypeStore createStaticTypes() { 903 List<Class<?>> concreteClazzes = 904 Arrays.asList( 905 AbstractMessage.class, 906 After.class, 907 AfterClass.class, 908 Any.class, 909 ApiClientHeaderProvider.class, 910 ApiStreamObserver.class, 911 Arrays.class, 912 Assert.class, 913 Before.class, 914 BeforeClass.class, 915 BidiStreamingCallable.class, 916 ClientStreamingCallable.class, 917 ExecutionException.class, 918 Generated.class, 919 IOException.class, 920 InvalidArgumentException.class, 921 List.class, 922 Lists.class, 923 NoCredentialsProvider.class, 924 Operation.class, 925 ServerStreamingCallable.class, 926 StatusCode.class, 927 Test.class, 928 UUID.class); 929 return new TypeStore(concreteClazzes); 930 } 931 addDynamicTypes(GapicContext context, Service service, TypeStore typeStore)932 private void addDynamicTypes(GapicContext context, Service service, TypeStore typeStore) { 933 typeStore.putAll( 934 service.pakkage(), 935 Arrays.asList( 936 ClassNames.getMockServiceClassName(service), 937 ClassNames.getServiceClientClassName(service), 938 ClassNames.getServiceSettingsClassName(service))); 939 String stubPakkage = String.format("%s.stub", service.pakkage()); 940 typeStore.put( 941 stubPakkage, getTransportContext().classNames().getTransportServiceStubClassName(service)); 942 // Pagination types. 943 typeStore.putAll( 944 service.pakkage(), 945 service.methods().stream() 946 .filter(m -> m.isPaged()) 947 .map(m -> String.format(PAGED_RESPONSE_TYPE_NAME_PATTERN, m.name())) 948 .collect(Collectors.toList()), 949 true, 950 ClassNames.getServiceClientClassName(service)); 951 for (Service mixinService : context.mixinServices()) { 952 typeStore.put(mixinService.pakkage(), ClassNames.getMockServiceClassName(mixinService)); 953 for (Method mixinMethod : mixinService.methods()) { 954 if (!mixinMethod.isPaged()) { 955 continue; 956 } 957 typeStore.put( 958 service.pakkage(), 959 String.format(PAGED_RESPONSE_TYPE_NAME_PATTERN, mixinMethod.name()), 960 true, 961 ClassNames.getServiceClientClassName(service)); 962 } 963 } 964 } 965 getCallableType(Method protoMethod)966 protected static TypeNode getCallableType(Method protoMethod) { 967 Preconditions.checkState( 968 !protoMethod.stream().equals(Method.Stream.NONE), 969 "No callable type exists for non-streaming methods."); 970 971 Class<?> callableClazz = ClientStreamingCallable.class; 972 switch (protoMethod.stream()) { 973 case BIDI: 974 callableClazz = BidiStreamingCallable.class; 975 break; 976 case SERVER: 977 callableClazz = ServerStreamingCallable.class; 978 break; 979 case CLIENT: 980 // Fall through. 981 case NONE: 982 // Fall through 983 default: 984 // Fall through 985 } 986 987 List<Reference> generics = new ArrayList<>(); 988 generics.add(protoMethod.inputType().reference()); 989 generics.add(protoMethod.outputType().reference()); 990 991 return TypeNode.withReference( 992 ConcreteReference.builder().setClazz(callableClazz).setGenerics(generics).build()); 993 } 994 getPagedResponseType(Method method, Service service)995 private static TypeNode getPagedResponseType(Method method, Service service) { 996 return TypeNode.withReference( 997 VaporReference.builder() 998 .setName(String.format(PAGED_RESPONSE_TYPE_NAME_PATTERN, method.name())) 999 .setPakkage(service.pakkage()) 1000 .setEnclosingClassNames(ClassNames.getServiceClientClassName(service)) 1001 .setIsStaticImport(true) 1002 .build()); 1003 } 1004 getCallableMethodName(Method protoMethod)1005 protected static String getCallableMethodName(Method protoMethod) { 1006 Preconditions.checkState( 1007 !protoMethod.stream().equals(Method.Stream.NONE), 1008 "No callable type exists for non-streaming methods."); 1009 1010 switch (protoMethod.stream()) { 1011 case BIDI: 1012 return "bidiStreamingCall"; 1013 case SERVER: 1014 return "serverStreamingCall"; 1015 case CLIENT: 1016 // Fall through. 1017 case NONE: 1018 // Fall through 1019 default: 1020 return "clientStreamingCall"; 1021 } 1022 } 1023 getMockServiceVarName(Service service)1024 protected String getMockServiceVarName(Service service) { 1025 return String.format(MOCK_SERVICE_VAR_NAME_PATTERN, service.name()); 1026 } 1027 isProtoEmptyType(TypeNode type)1028 private static boolean isProtoEmptyType(TypeNode type) { 1029 return type.reference().pakkage().equals("com.google.protobuf") 1030 && type.reference().name().equals("Empty"); 1031 } 1032 } 1033