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