Skip to content

Commit 35793a7

Browse files
authored
[ggj][codegen][ast] fix: solidify codegen method order with AST Comparables (#311)
* feat: add protobuf comment parser util * fix: add basic proto build rules * feat: add header comments to ServiceClient * fix: build protoc at test time * fix!: wrap protobuf location and process comments * fix: solidify codegen method order with TypeNode/MethodArg and Comparable * fix: clean up tests
1 parent 39918f1 commit 35793a7

File tree

7 files changed

+155
-18
lines changed

7 files changed

+155
-18
lines changed

src/main/java/com/google/api/generator/engine/ast/TypeNode.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import javax.annotation.Nullable;
2424

2525
@AutoValue
26-
public abstract class TypeNode implements AstNode {
26+
public abstract class TypeNode implements AstNode, Comparable<TypeNode> {
2727
static final Reference EXCEPTION_REFERENCE = ConcreteReference.withClazz(Exception.class);
2828
public static final Reference WILDCARD_REFERENCE = ConcreteReference.wildcard();
2929

@@ -88,6 +88,31 @@ public enum TypeKind {
8888
@Nullable
8989
public abstract Reference reference();
9090

91+
@Override
92+
public int compareTo(TypeNode other) {
93+
// Ascending order of name.
94+
if (isPrimitiveType()) {
95+
if (other.isPrimitiveType()) {
96+
return typeKind().name().compareTo(other.typeKind().name());
97+
}
98+
// b is a reference type or null, so a < b.
99+
return -1;
100+
}
101+
102+
if (this.equals(TypeNode.NULL)) {
103+
// Can't self-compare, so we don't need to check whether the other one is TypeNode.NULL.
104+
return other.isPrimitiveType() ? 1 : -1;
105+
}
106+
107+
if (other.isPrimitiveType() || other.equals(TypeNode.NULL)) {
108+
return 1;
109+
}
110+
111+
// Both are reference types.
112+
// TODO(miraleung): Replace this with a proper reference Comaparator.
113+
return reference().fullName().compareTo(other.reference().fullName());
114+
}
115+
91116
public static Builder builder() {
92117
return new AutoValue_TypeNode.Builder().setIsArray(false);
93118
}

src/main/java/com/google/api/generator/gapic/composer/ServiceClientClassComposer.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,26 @@ private static List<MethodDefinition> createMethodVariants(
476476
Preconditions.checkNotNull(
477477
inputMessage, String.format("Message %s not found", methodInputTypeName));
478478

479-
for (List<MethodArgument> signature : method.methodSignatures()) {
479+
// Make the method signature order deterministic, which helps with unit testing and per-version
480+
// diffs.
481+
List<List<MethodArgument>> sortedMethodSignatures =
482+
method.methodSignatures().stream()
483+
.sorted(
484+
(s1, s2) -> {
485+
if (s1.size() != s2.size()) {
486+
return s1.size() - s2.size();
487+
}
488+
for (int i = 0; i < s1.size(); i++) {
489+
int compareVal = s1.get(i).compareTo(s2.get(i));
490+
if (compareVal != 0) {
491+
return compareVal;
492+
}
493+
}
494+
return 0;
495+
})
496+
.collect(Collectors.toList());
497+
498+
for (List<MethodArgument> signature : sortedMethodSignatures) {
480499
// Get the argument list.
481500
List<VariableExpr> arguments =
482501
signature.stream()

src/main/java/com/google/api/generator/gapic/model/MethodArgument.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import java.util.List;
2121

2222
@AutoValue
23-
public abstract class MethodArgument {
23+
public abstract class MethodArgument implements Comparable<MethodArgument> {
2424
public abstract String name();
2525

2626
public abstract TypeNode type();
@@ -32,6 +32,15 @@ public abstract class MethodArgument {
3232
// Returns true if this is a resource name helper tyep.
3333
public abstract boolean isResourceNameHelper();
3434

35+
@Override
36+
public int compareTo(MethodArgument other) {
37+
int compareVal = type().compareTo(other.type());
38+
if (compareVal == 0) {
39+
compareVal = name().compareTo(other.name());
40+
}
41+
return compareVal;
42+
}
43+
3544
public static Builder builder() {
3645
return new AutoValue_MethodArgument.Builder()
3746
.setNestedTypes(ImmutableList.of())

src/test/java/com/google/api/generator/engine/ast/TypeNodeTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.google.api.generator.engine.ast;
1616

17+
import static com.google.common.truth.Truth.assertThat;
1718
import static junit.framework.Assert.assertFalse;
1819
import static junit.framework.Assert.assertTrue;
1920
import static org.junit.Assert.assertThrows;
@@ -112,6 +113,36 @@ public void type_wildcardUpperBoundGenerics() {
112113
.build());
113114
}
114115

116+
@Test
117+
public void compareTypes() {
118+
// Primitive and primitive.
119+
// Can't compare objects to themselves, so this test is omitted.
120+
assertThat(TypeNode.INT.compareTo(TypeNode.BOOLEAN)).isGreaterThan(0);
121+
assertThat(TypeNode.BOOLEAN.compareTo(TypeNode.INT)).isLessThan(0);
122+
123+
// Primitive and null.
124+
assertThat(TypeNode.INT.compareTo(TypeNode.NULL)).isLessThan(0);
125+
assertThat(TypeNode.NULL.compareTo(TypeNode.INT)).isGreaterThan(0);
126+
127+
// Primitive and reference.
128+
assertThat(TypeNode.INT.compareTo(TypeNode.INT_OBJECT)).isLessThan(0);
129+
assertThat(TypeNode.INT.compareTo(TypeNode.STRING)).isLessThan(0);
130+
assertThat(TypeNode.INT_OBJECT.compareTo(TypeNode.INT)).isGreaterThan(0);
131+
132+
// Reference and null.
133+
// No test for null against null because we can't compare objects to themselves.
134+
assertThat(TypeNode.INT_OBJECT.compareTo(TypeNode.NULL)).isGreaterThan(0);
135+
assertThat(TypeNode.NULL.compareTo(TypeNode.BOOLEAN_OBJECT)).isLessThan(0);
136+
137+
// Reference and reference. Sorted alphabetically by package.
138+
assertThat(TypeNode.BOOLEAN_OBJECT.compareTo(TypeNode.INT_OBJECT)).isLessThan(0);
139+
assertThat(TypeNode.BOOLEAN_OBJECT.compareTo(TypeNode.STRING)).isLessThan(0);
140+
assertThat(
141+
TypeNode.BOOLEAN_OBJECT.compareTo(
142+
TypeNode.withReference(ConcreteReference.withClazz(Arrays.class))))
143+
.isLessThan(0);
144+
}
145+
115146
@Test
116147
public void invalidType_topLevelWildcard() {
117148
assertThrows(

src/test/java/com/google/api/generator/gapic/composer/ServiceClientClassComposerTest.java

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,11 @@ public void generateServiceClasses() {
203203
+ " return operationsClient;\n"
204204
+ " }\n"
205205
+ "\n"
206-
+ " public final EchoResponse echo(String content) {\n"
207-
+ " EchoRequest request = EchoRequest.newBuilder().setContent(content).build();\n"
206+
+ " public final EchoResponse echo(ResourceName parent) {\n"
207+
+ " EchoRequest request =\n"
208+
+ " EchoRequest.newBuilder()\n"
209+
+ " .setParent(Strings.isNullOrEmpty(parent) ? null : parent.toString())\n"
210+
+ " .build();\n"
208211
+ " return echo(request);\n"
209212
+ " }\n"
210213
+ "\n"
@@ -213,22 +216,21 @@ public void generateServiceClasses() {
213216
+ " return echo(request);\n"
214217
+ " }\n"
215218
+ "\n"
216-
+ " public final EchoResponse echo(String content, Severity severity) {\n"
219+
+ " public final EchoResponse echo(FoobarName name) {\n"
217220
+ " EchoRequest request =\n"
218-
+ " EchoRequest.newBuilder().setContent(content).setSeverity(severity).build();\n"
221+
+ " EchoRequest.newBuilder()\n"
222+
+ " .setName(Strings.isNullOrEmpty(name) ? null : name.toString())\n"
223+
+ " .build();\n"
219224
+ " return echo(request);\n"
220225
+ " }\n"
221226
+ "\n"
222-
+ " public final EchoResponse echo(String name) {\n"
223-
+ " EchoRequest request = EchoRequest.newBuilder().setName(name).build();\n"
227+
+ " public final EchoResponse echo(String content) {\n"
228+
+ " EchoRequest request = EchoRequest.newBuilder().setContent(content).build();\n"
224229
+ " return echo(request);\n"
225230
+ " }\n"
226231
+ "\n"
227-
+ " public final EchoResponse echo(FoobarName name) {\n"
228-
+ " EchoRequest request =\n"
229-
+ " EchoRequest.newBuilder()\n"
230-
+ " .setName(Strings.isNullOrEmpty(name) ? null : name.toString())\n"
231-
+ " .build();\n"
232+
+ " public final EchoResponse echo(String name) {\n"
233+
+ " EchoRequest request = EchoRequest.newBuilder().setName(name).build();\n"
232234
+ " return echo(request);\n"
233235
+ " }\n"
234236
+ "\n"
@@ -237,11 +239,9 @@ public void generateServiceClasses() {
237239
+ " return echo(request);\n"
238240
+ " }\n"
239241
+ "\n"
240-
+ " public final EchoResponse echo(ResourceName parent) {\n"
242+
+ " public final EchoResponse echo(String content, Severity severity) {\n"
241243
+ " EchoRequest request =\n"
242-
+ " EchoRequest.newBuilder()\n"
243-
+ " .setParent(Strings.isNullOrEmpty(parent) ? null : parent.toString())\n"
244-
+ " .build();\n"
244+
+ " EchoRequest.newBuilder().setContent(content).setSeverity(severity).build();\n"
245245
+ " return echo(request);\n"
246246
+ " }\n"
247247
+ "\n"

src/test/java/com/google/api/generator/gapic/model/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package(default_visibility = ["//visibility:public"])
22

33
TESTS = [
44
"GapicServiceConfigTest",
5+
"MethodArgumentTest",
56
"MethodTest",
67
]
78

@@ -20,6 +21,7 @@ filegroup(
2021
deps = [
2122
"//:service_config_java_proto",
2223
"//src/main/java/com/google/api/generator:autovalue",
24+
"//src/main/java/com/google/api/generator/engine/ast",
2325
"//src/main/java/com/google/api/generator/gapic/model",
2426
"//src/main/java/com/google/api/generator/gapic/protoparser",
2527
"//src/test/java/com/google/api/generator/gapic/testdata:showcase_java_proto",
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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.model;
16+
17+
import static com.google.common.truth.Truth.assertThat;
18+
19+
import com.google.api.generator.engine.ast.TypeNode;
20+
import java.util.function.BiFunction;
21+
import java.util.function.Function;
22+
import org.junit.Test;
23+
24+
public class MethodArgumentTest {
25+
@Test
26+
public void compareMethodArguments() {
27+
BiFunction<String, TypeNode, MethodArgument> methodArgFn =
28+
(name, type) -> MethodArgument.builder().setName(name).setType(type).build();
29+
30+
// Cursory sanity-check of type-only differences, since these are already covered in the
31+
// TypeNode test.
32+
assertThat(
33+
methodArgFn
34+
.apply("foo", TypeNode.INT)
35+
.compareTo(methodArgFn.apply("foo", TypeNode.BOOLEAN)))
36+
.isGreaterThan(0);
37+
assertThat(
38+
methodArgFn
39+
.apply("foo", TypeNode.INT)
40+
.compareTo(methodArgFn.apply("foo", TypeNode.INT_OBJECT)))
41+
.isLessThan(0);
42+
43+
// Non-type-based differences.
44+
Function<String, MethodArgument> simpleMethodArgFn =
45+
(name) -> methodArgFn.apply(name, TypeNode.INT);
46+
assertThat(simpleMethodArgFn.apply("foo").compareTo(simpleMethodArgFn.apply("bar")))
47+
.isGreaterThan(0);
48+
assertThat(simpleMethodArgFn.apply("bar").compareTo(simpleMethodArgFn.apply("foo")))
49+
.isLessThan(0);
50+
}
51+
}

0 commit comments

Comments
 (0)