• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2016 Google, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.common.truth.extensions.proto;
17 
18 import static com.google.common.base.Preconditions.checkArgument;
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 import static com.google.common.truth.TruthFailureSubject.truthFailures;
22 import static com.google.protobuf.ExtensionRegistry.getEmptyRegistry;
23 
24 import com.google.common.base.Preconditions;
25 import com.google.common.collect.ImmutableList;
26 import com.google.common.collect.ImmutableMap;
27 import com.google.common.collect.ImmutableMultimap;
28 import com.google.common.collect.ImmutableSet;
29 import com.google.common.collect.Multimap;
30 import com.google.common.collect.Sets;
31 import com.google.common.truth.Expect;
32 import com.google.common.truth.ExpectFailure;
33 import com.google.common.truth.Subject;
34 import com.google.common.truth.TruthFailureSubject;
35 import com.google.protobuf.Descriptors.FieldDescriptor;
36 import com.google.protobuf.ExtensionRegistry;
37 import com.google.protobuf.InvalidProtocolBufferException;
38 import com.google.protobuf.Message;
39 import com.google.protobuf.TextFormat;
40 import com.google.protobuf.TextFormat.ParseException;
41 import com.google.protobuf.TypeRegistry;
42 import com.google.protobuf.UnknownFieldSet;
43 import java.lang.reflect.Method;
44 import java.util.Collection;
45 import java.util.Map;
46 import java.util.Set;
47 import java.util.regex.Pattern;
48 import org.checkerframework.checker.nullness.qual.Nullable;
49 import org.junit.Rule;
50 
51 /** Base class for testing {@link ProtoSubject} methods. */
52 public class ProtoSubjectTestBase {
53 
54   // Type information for subclasses.
55   static enum TestType {
56     IMMUTABLE_PROTO2(TestMessage2.getDefaultInstance()),
57     PROTO3(TestMessage3.getDefaultInstance());
58 
59     private final Message defaultInstance;
60 
TestType(Message defaultInstance)61     TestType(Message defaultInstance) {
62       this.defaultInstance = defaultInstance;
63     }
64 
defaultInstance()65     public Message defaultInstance() {
66       return defaultInstance;
67     }
68 
isProto3()69     public boolean isProto3() {
70       return this == PROTO3;
71     }
72   }
73 
74   private static final TypeRegistry typeRegistry =
75       TypeRegistry.newBuilder()
76           .add(TestMessage3.getDescriptor())
77           .add(TestMessage2.getDescriptor())
78           .build();
79 
80   private static final TextFormat.Parser PARSER =
81       TextFormat.Parser.newBuilder()
82           .setSingularOverwritePolicy(
83               TextFormat.Parser.SingularOverwritePolicy.FORBID_SINGULAR_OVERWRITES)
84           .setTypeRegistry(typeRegistry)
85           .build();
86 
87   private static final ExtensionRegistry extensionRegistry = getEmptyRegistry();
88 
89   // For Parameterized testing.
parameters()90   protected static Collection<Object[]> parameters() {
91     ImmutableList.Builder<Object[]> builder = ImmutableList.builder();
92     for (TestType testType : TestType.values()) {
93       builder.add(new Object[] {testType});
94     }
95     return builder.build();
96   }
97 
98   @Rule public final Expect expect = Expect.create();
99 
100   // Hackhackhack: 'ExpectFailure' does not support more than one call per test, but we have many
101   // tests which require it.  So, we create an arbitrary number of these rules, and dole them out
102   // in order on demand.
103   // TODO(user): See if 'expectFailure.enterRuleContext()' could be made public, or a '.reset()'
104   // function could be added to mitigate the need for this.  Alternatively, if & when Truth moves
105   // to Java 8, we can use the static API with lambdas instead.
106   @Rule public final MultiExpectFailure multiExpectFailure = new MultiExpectFailure(/* size= */ 20);
107 
108   private final Message defaultInstance;
109   private final boolean isProto3;
110 
ProtoSubjectTestBase(TestType testType)111   protected ProtoSubjectTestBase(TestType testType) {
112     this.defaultInstance = testType.defaultInstance();
113     this.isProto3 = testType.isProto3();
114   }
115 
fromUnknownFields(UnknownFieldSet unknownFieldSet)116   protected final Message fromUnknownFields(UnknownFieldSet unknownFieldSet)
117       throws InvalidProtocolBufferException {
118     return defaultInstance.getParserForType().parseFrom(unknownFieldSet.toByteArray());
119   }
120 
fullMessageName()121   protected final String fullMessageName() {
122     return defaultInstance.getDescriptorForType().getFullName();
123   }
124 
getFieldDescriptor(String fieldName)125   protected final FieldDescriptor getFieldDescriptor(String fieldName) {
126     FieldDescriptor fieldDescriptor =
127         defaultInstance.getDescriptorForType().findFieldByName(fieldName);
128     checkArgument(fieldDescriptor != null, "No field named %s.", fieldName);
129     return fieldDescriptor;
130   }
131 
getFieldNumber(String fieldName)132   protected final int getFieldNumber(String fieldName) {
133     return getFieldDescriptor(fieldName).getNumber();
134   }
135 
getTypeRegistry()136   protected final TypeRegistry getTypeRegistry() {
137     return typeRegistry;
138   }
139 
getExtensionRegistry()140   protected final ExtensionRegistry getExtensionRegistry() {
141     return extensionRegistry;
142   }
143 
clone(Message in)144   protected final Message clone(Message in) {
145     return in.toBuilder().build();
146   }
147 
parse(String textProto)148   protected Message parse(String textProto) {
149     try {
150       Message.Builder builder = defaultInstance.toBuilder();
151       PARSER.merge(textProto, builder);
152       return builder.build();
153     } catch (ParseException e) {
154       throw new RuntimeException(e);
155     }
156   }
157 
parsePartial(String textProto)158   protected final Message parsePartial(String textProto) {
159     try {
160       Message.Builder builder = defaultInstance.toBuilder();
161       PARSER.merge(textProto, builder);
162       return builder.buildPartial();
163     } catch (ParseException e) {
164       throw new RuntimeException(e);
165     }
166   }
167 
isProto3()168   protected final boolean isProto3() {
169     return isProto3;
170   }
171 
172   /**
173    * Some tests don't vary across the different proto types, and should only be run once.
174    *
175    * <p>This method returns true for exactly one {@link TestType}, and false for all the others, and
176    * so can be used to ensure tests are only run once.
177    */
testIsRunOnce()178   protected final boolean testIsRunOnce() {
179     return isProto3;
180   }
181 
expectFailureWhenTesting()182   protected final ProtoSubjectBuilder expectFailureWhenTesting() {
183     return multiExpectFailure.whenTesting().about(ProtoTruth.protos());
184   }
185 
expectThatFailure()186   protected final TruthFailureSubject expectThatFailure() {
187     return expect.about(truthFailures()).that(multiExpectFailure.getFailure());
188   }
189 
expectThat(@ullable Message message)190   protected final ProtoSubject expectThat(@Nullable Message message) {
191     return expect.about(ProtoTruth.protos()).that(message);
192   }
193 
expectThat(Iterable<M> messages)194   protected final <M extends Message> IterableOfProtosSubject<M> expectThat(Iterable<M> messages) {
195     return expect.about(ProtoTruth.protos()).that(messages);
196   }
197 
expectThat(Map<?, M> map)198   protected final <M extends Message> MapWithProtoValuesSubject<M> expectThat(Map<?, M> map) {
199     return expect.about(ProtoTruth.protos()).that(map);
200   }
201 
expectThat( Multimap<?, M> multimap)202   protected final <M extends Message> MultimapWithProtoValuesSubject<M> expectThat(
203       Multimap<?, M> multimap) {
204     return expect.about(ProtoTruth.protos()).that(multimap);
205   }
206 
expectThatWithMessage(String msg, @Nullable Message message)207   protected final ProtoSubject expectThatWithMessage(String msg, @Nullable Message message) {
208     return expect.withMessage(msg).about(ProtoTruth.protos()).that(message);
209   }
210 
expectIsEqualToFailed()211   protected final void expectIsEqualToFailed() {
212     expectFailureMatches(
213         "Not true that messages compare equal\\.\\s*"
214             + "(Differences were found:\\n.*|No differences were reported\\..*)");
215   }
216 
expectIsNotEqualToFailed()217   protected final void expectIsNotEqualToFailed() {
218     expectFailureMatches(
219         "Not true that messages compare not equal\\.\\s*"
220             + "(Only ignorable differences were found:\\n.*|"
221             + "No differences were found\\..*)");
222   }
223 
224   /**
225    * Expects the current {@link ExpectFailure} failure message to match the provided regex, using
226    * {@code Pattern.DOTALL} to match newlines.
227    */
expectFailureMatches(String regex)228   protected final void expectFailureMatches(String regex) {
229     expectThatFailure().hasMessageThat().matches(Pattern.compile(regex, Pattern.DOTALL));
230   }
231 
232   /**
233    * Expects the current {@link ExpectFailure} failure message to NOT match the provided regex,
234    * using {@code Pattern.DOTALL} to match newlines.
235    */
expectNoRegex(Throwable t, String regex)236   protected final void expectNoRegex(Throwable t, String regex) {
237     expectThatFailure().hasMessageThat().doesNotMatch(Pattern.compile(regex, Pattern.DOTALL));
238   }
239 
listOf(T... elements)240   protected static final <T> ImmutableList<T> listOf(T... elements) {
241     return ImmutableList.copyOf(elements);
242   }
243 
arrayOf(T... elements)244   protected static final <T> T[] arrayOf(T... elements) {
245     return elements;
246   }
247 
248   @SuppressWarnings("unchecked")
mapOf(K k0, V v0, Object... rest)249   protected static final <K, V> ImmutableMap<K, V> mapOf(K k0, V v0, Object... rest) {
250     Preconditions.checkArgument(rest.length % 2 == 0, "Uneven args: %s", rest.length);
251 
252     ImmutableMap.Builder<K, V> builder = new ImmutableMap.Builder<>();
253     builder.put(k0, v0);
254     for (int i = 0; i < rest.length; i += 2) {
255       builder.put((K) rest[i], (V) rest[i + 1]);
256     }
257     return builder.buildOrThrow();
258   }
259 
260   @SuppressWarnings("unchecked")
multimapOf(K k0, V v0, Object... rest)261   protected static final <K, V> ImmutableMultimap<K, V> multimapOf(K k0, V v0, Object... rest) {
262     Preconditions.checkArgument(rest.length % 2 == 0, "Uneven args: %s", rest.length);
263 
264     ImmutableMultimap.Builder<K, V> builder = new ImmutableMultimap.Builder<>();
265     builder.put(k0, v0);
266     for (int i = 0; i < rest.length; i += 2) {
267       builder.put((K) rest[i], (V) rest[i + 1]);
268     }
269     return builder.build();
270   }
271 
checkMethodNamesEndWithForValues( Class<?> clazz, Class<? extends Subject> pseudoSuperclass)272   final void checkMethodNamesEndWithForValues(
273       Class<?> clazz, Class<? extends Subject> pseudoSuperclass) {
274     // Don't run this test twice.
275     if (!testIsRunOnce()) {
276       return;
277     }
278 
279     Set<String> diff = Sets.difference(getMethodNames(clazz), getMethodNames(pseudoSuperclass));
280     assertWithMessage("Found no methods to test. Bug in test?").that(diff).isNotEmpty();
281     for (String methodName : diff) {
282       assertThat(methodName).endsWith("ForValues");
283     }
284   }
285 
getMethodNames(Class<?> clazz)286   private static ImmutableSet<String> getMethodNames(Class<?> clazz) {
287     ImmutableSet.Builder<String> names = ImmutableSet.builder();
288     for (Method method : clazz.getMethods()) {
289       names.add(method.getName());
290     }
291     return names.build();
292   }
293 }
294