• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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