Line data Source code
1 : // Copyright (C) 2018 The Android Open Source Project 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.gerrit.proto.testing; 16 : 17 : import static com.google.common.collect.ImmutableMap.toImmutableMap; 18 : import static com.google.common.truth.Fact.simpleFact; 19 : import static com.google.common.truth.Truth.assertAbout; 20 : 21 : import com.google.common.truth.FailureMetadata; 22 : import com.google.common.truth.Subject; 23 : import java.lang.reflect.Field; 24 : import java.lang.reflect.Method; 25 : import java.lang.reflect.Modifier; 26 : import java.lang.reflect.Type; 27 : import java.util.Arrays; 28 : import java.util.Map; 29 : import org.apache.commons.lang3.reflect.FieldUtils; 30 : 31 : /** 32 : * Subject about classes that are serialized into persistent caches or indices. 33 : * 34 : * <p>Hand-written {@link com.google.gerrit.server.cache.serialize.CacheSerializer CacheSerializer} 35 : * and {@link com.google.gerrit.entities.converter.ProtoConverter ProtoConverter} implementations 36 : * depend on the exact representation of the data stored in a class, so it is important to verify 37 : * any assumptions about the structure of the serialized classes. This class contains assertions 38 : * about serialized classes, and should be used for every class that has a custom serializer 39 : * implementation. 40 : * 41 : * <p>Changing fields of a serialized class (or abstract methods, in the case of {@code @AutoValue} 42 : * classes) will likely require changes to the serializer implementation, and may require bumping 43 : * the {@link com.google.gerrit.server.cache.PersistentCacheBinding#version(int) version} in the 44 : * cache binding, in case the representation has changed in such a way that old serialized data 45 : * becomes unreadable. 46 : * 47 : * <p>Changes to a serialized class such as adding or removing fields generally requires a change to 48 : * the hand-written serializer. Usually, serializer implementations should be written in such a way 49 : * that new fields are considered optional, and won't require bumping the version. 50 : */ 51 : public class SerializedClassSubject extends Subject { 52 : public static SerializedClassSubject assertThatSerializedClass(Class<?> actual) { 53 : // This formulation fails in Eclipse 4.7.3a with "The type 54 : // SerializedClassSubject does not define SerializedClassSubject() that is 55 : // applicable here", due to 56 : // https://bugs.eclipse.org/bugs/show_bug.cgi?id=534694 or a similar bug: 57 : // return assertAbout(SerializedClassSubject::new).that(actual); 58 4 : Subject.Factory<SerializedClassSubject, Class<?>> factory = 59 4 : (m, a) -> new SerializedClassSubject(m, a); 60 4 : return assertAbout(factory).that(actual); 61 : } 62 : 63 : private final Class<?> clazz; 64 : 65 : private SerializedClassSubject(FailureMetadata metadata, Class<?> clazz) { 66 4 : super(metadata, clazz); 67 4 : this.clazz = clazz; 68 4 : } 69 : 70 : public void isAbstract() { 71 3 : isNotNull(); 72 3 : if (!Modifier.isAbstract(clazz.getModifiers())) { 73 0 : failWithActual(simpleFact("expected class to be abstract")); 74 : } 75 3 : } 76 : 77 : public void isConcrete() { 78 3 : isNotNull(); 79 3 : if (Modifier.isAbstract(clazz.getModifiers())) { 80 0 : failWithActual(simpleFact("expected class to be concrete")); 81 : } 82 3 : } 83 : 84 : public void hasFields(Map<String, Type> expectedFields) { 85 3 : isConcrete(); 86 3 : check("fields()") 87 3 : .that( 88 3 : FieldUtils.getAllFieldsList(clazz).stream() 89 3 : .filter(f -> !Modifier.isStatic(f.getModifiers())) 90 3 : .collect(toImmutableMap(Field::getName, Field::getGenericType))) 91 3 : .containsExactlyEntriesIn(expectedFields); 92 3 : } 93 : 94 : public void hasAutoValueMethods(Map<String, Type> expectedMethods) { 95 : // Would be nice if we could check clazz is an @AutoValue, but the retention is not RUNTIME. 96 3 : isAbstract(); 97 3 : check("noArgumentAbstractMethods()") 98 3 : .that( 99 3 : Arrays.stream(clazz.getDeclaredMethods()) 100 3 : .filter(m -> !Modifier.isStatic(m.getModifiers())) 101 3 : .filter(m -> Modifier.isAbstract(m.getModifiers())) 102 3 : .filter(m -> m.getParameters().length == 0) 103 3 : .collect(toImmutableMap(Method::getName, Method::getGenericReturnType))) 104 3 : .isEqualTo(expectedMethods); 105 3 : } 106 : 107 : public void extendsClass(Type superclassType) { 108 1 : isNotNull(); 109 1 : check("getGenericSuperclass()").that(clazz.getGenericSuperclass()).isEqualTo(superclassType); 110 1 : } 111 : }