1 /* 2 * Copyright 2014 Google LLC 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.auto.value.gwt; 17 18 import static com.google.common.truth.Truth.assertThat; 19 import static com.google.common.truth.Truth.assertWithMessage; 20 21 import com.google.auto.value.AutoValue; 22 import com.google.common.annotations.GwtCompatible; 23 import com.google.common.collect.ImmutableList; 24 import com.google.common.collect.ImmutableMap; 25 import com.google.common.reflect.Reflection; 26 import com.google.gwt.user.client.rpc.SerializationException; 27 import com.google.gwt.user.client.rpc.SerializationStreamWriter; 28 import java.io.Serializable; 29 import java.lang.reflect.Method; 30 import java.util.ArrayDeque; 31 import java.util.Collections; 32 import java.util.Deque; 33 import java.util.List; 34 import java.util.Map; 35 import javax.annotation.Nullable; 36 import org.junit.Test; 37 import org.junit.function.ThrowingRunnable; 38 import org.junit.runner.RunWith; 39 import org.junit.runners.JUnit4; 40 41 /** 42 * Tests that the generated GWT serializer for GwtValueType serializes fields in the expected way. 43 * 44 * @author emcmanus@google.com (Éamonn McManus) 45 */ 46 @RunWith(JUnit4.class) 47 public class CustomFieldSerializerTest { 48 @AutoValue 49 @GwtCompatible(serializable = true) 50 abstract static class ValueType implements Serializable { string()51 abstract String string(); 52 integer()53 abstract int integer(); 54 55 @Nullable other()56 abstract ValueType other(); 57 others()58 abstract List<ValueType> others(); 59 create(String string, int integer, @Nullable ValueType other)60 static ValueType create(String string, int integer, @Nullable ValueType other) { 61 return create(string, integer, other, Collections.<ValueType>emptyList()); 62 } 63 create( String string, int integer, @Nullable ValueType other, List<ValueType> others)64 static ValueType create( 65 String string, int integer, @Nullable ValueType other, List<ValueType> others) { 66 return new AutoValue_CustomFieldSerializerTest_ValueType(string, integer, other, others); 67 } 68 } 69 70 private static final ValueType SIMPLE = ValueType.create("anotherstring", 1729, null); 71 private static final ValueType CONS = ValueType.create("whatever", 1296, SIMPLE); 72 private static final ValueType WITH_LIST = 73 ValueType.create("blim", 11881376, SIMPLE, ImmutableList.of(SIMPLE, CONS)); 74 75 private final MickeyMouseMock<SerializationStreamWriter> mock = 76 new MickeyMouseMock<>(SerializationStreamWriter.class); 77 private final SerializationStreamWriter streamWriter = mock.proxy(); 78 79 @Test testCustomFieldSerializer()80 public void testCustomFieldSerializer() throws SerializationException { 81 AutoValue_CustomFieldSerializerTest_ValueType withList = 82 (AutoValue_CustomFieldSerializerTest_ValueType) WITH_LIST; 83 AutoValue_CustomFieldSerializerTest_ValueType_CustomFieldSerializer.serialize( 84 streamWriter, withList); 85 mock.verify( 86 () -> { 87 streamWriter.writeString("blim"); 88 streamWriter.writeInt(11881376); 89 streamWriter.writeObject(SIMPLE); 90 streamWriter.writeObject(ImmutableList.of(SIMPLE, CONS)); 91 }); 92 } 93 94 @AutoValue 95 @GwtCompatible(serializable = true) 96 abstract static class ValueTypeWithGetters implements Serializable { getPackage()97 abstract String getPackage(); 98 isDefault()99 abstract boolean isDefault(); 100 create(String pkg, boolean dflt)101 static ValueTypeWithGetters create(String pkg, boolean dflt) { 102 return new AutoValue_CustomFieldSerializerTest_ValueTypeWithGetters(pkg, dflt); 103 } 104 } 105 106 @Test testCustomFieldSerializerWithGetters()107 public void testCustomFieldSerializerWithGetters() throws SerializationException { 108 AutoValue_CustomFieldSerializerTest_ValueTypeWithGetters instance = 109 (AutoValue_CustomFieldSerializerTest_ValueTypeWithGetters) 110 ValueTypeWithGetters.create("package", true); 111 AutoValue_CustomFieldSerializerTest_ValueTypeWithGetters_CustomFieldSerializer.serialize( 112 streamWriter, instance); 113 mock.verify( 114 () -> { 115 streamWriter.writeString("package"); 116 streamWriter.writeBoolean(true); 117 }); 118 } 119 120 @AutoValue 121 @GwtCompatible(serializable = true) 122 abstract static class GenericValueType<K extends Comparable<K>, V extends K> 123 implements Serializable { map()124 abstract Map<K, V> map(); 125 create(Map<K, V> map)126 static <K extends Comparable<K>, V extends K> GenericValueType<K, V> create(Map<K, V> map) { 127 return new AutoValue_CustomFieldSerializerTest_GenericValueType<K, V>(map); 128 } 129 } 130 131 @Test testCustomFieldSerializerGeneric()132 public void testCustomFieldSerializerGeneric() throws SerializationException { 133 Map<Integer, Integer> map = ImmutableMap.of(2, 2); 134 AutoValue_CustomFieldSerializerTest_GenericValueType<Integer, Integer> instance = 135 (AutoValue_CustomFieldSerializerTest_GenericValueType<Integer, Integer>) 136 GenericValueType.create(map); 137 AutoValue_CustomFieldSerializerTest_GenericValueType_CustomFieldSerializer.serialize( 138 streamWriter, instance); 139 mock.verify( 140 () -> { 141 streamWriter.writeObject(map); 142 }); 143 } 144 145 @AutoValue 146 @GwtCompatible(serializable = true) 147 abstract static class ValueTypeWithBuilder implements Serializable { string()148 abstract String string(); 149 strings()150 abstract ImmutableList<String> strings(); 151 builder()152 static Builder builder() { 153 return new AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilder.Builder(); 154 } 155 156 @AutoValue.Builder 157 interface Builder { string(String x)158 Builder string(String x); 159 strings(ImmutableList<String> x)160 Builder strings(ImmutableList<String> x); 161 build()162 ValueTypeWithBuilder build(); 163 } 164 } 165 166 @Test testCustomFieldSerializerWithBuilder()167 public void testCustomFieldSerializerWithBuilder() throws SerializationException { 168 AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilder instance = 169 (AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilder) 170 ValueTypeWithBuilder.builder().string("s").strings(ImmutableList.of("a", "b")).build(); 171 AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilder_CustomFieldSerializer.serialize( 172 streamWriter, instance); 173 mock.verify( 174 () -> { 175 streamWriter.writeString("s"); 176 streamWriter.writeObject(ImmutableList.of("a", "b")); 177 }); 178 } 179 180 @AutoValue 181 @GwtCompatible(serializable = true) 182 abstract static class ValueTypeWithBuilderAndGetters implements Serializable { getPackage()183 abstract String getPackage(); 184 isDefault()185 abstract boolean isDefault(); 186 builder()187 static Builder builder() { 188 return new AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilderAndGetters.Builder(); 189 } 190 191 @AutoValue.Builder 192 interface Builder { setPackage(String x)193 Builder setPackage(String x); 194 setDefault(boolean x)195 Builder setDefault(boolean x); 196 build()197 ValueTypeWithBuilderAndGetters build(); 198 } 199 } 200 201 @Test testCustomFieldSerializerWithBuilderAndGetters()202 public void testCustomFieldSerializerWithBuilderAndGetters() throws SerializationException { 203 AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilderAndGetters instance = 204 (AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilderAndGetters) 205 ValueTypeWithBuilderAndGetters.builder().setPackage("s").setDefault(false).build(); 206 AutoValue_CustomFieldSerializerTest_ValueTypeWithBuilderAndGetters_CustomFieldSerializer 207 .serialize(streamWriter, instance); 208 mock.verify( 209 () -> { 210 streamWriter.writeString("s"); 211 streamWriter.writeBoolean(false); 212 }); 213 } 214 215 @AutoValue 216 @GwtCompatible(serializable = true) 217 abstract static class GenericValueTypeWithBuilder<K extends Comparable<K>, V extends K> 218 implements Serializable { map()219 abstract Map<K, V> map(); 220 builder()221 static <K extends Comparable<K>, V extends K> Builder<K, V> builder() { 222 return new AutoValue_CustomFieldSerializerTest_GenericValueTypeWithBuilder.Builder<K, V>(); 223 } 224 225 @AutoValue.Builder 226 interface Builder<K extends Comparable<K>, V extends K> { map(Map<K, V> map)227 Builder<K, V> map(Map<K, V> map); 228 build()229 GenericValueTypeWithBuilder<K, V> build(); 230 } 231 } 232 233 @Test testCustomFieldSerializerGenericWithBuilder()234 public void testCustomFieldSerializerGenericWithBuilder() throws SerializationException { 235 Map<Integer, Integer> map = ImmutableMap.of(2, 2); 236 AutoValue_CustomFieldSerializerTest_GenericValueTypeWithBuilder<Integer, Integer> instance = 237 (AutoValue_CustomFieldSerializerTest_GenericValueTypeWithBuilder<Integer, Integer>) 238 GenericValueTypeWithBuilder.<Integer, Integer>builder().map(map).build(); 239 AutoValue_CustomFieldSerializerTest_GenericValueTypeWithBuilder_CustomFieldSerializer.serialize( 240 streamWriter, instance); 241 mock.verify( 242 () -> { 243 streamWriter.writeObject(map); 244 }); 245 } 246 247 @AutoValue 248 abstract static class MethodCall { method()249 abstract String method(); 250 args()251 abstract ImmutableList<Object> args(); 252 of(String method, ImmutableList<Object> args)253 static MethodCall of(String method, ImmutableList<Object> args) { 254 return new AutoValue_CustomFieldSerializerTest_MethodCall(method, args); 255 } 256 } 257 258 /** 259 * A trivial home-made mocking framework. 260 * 261 * <p>Mockito 5 no longer supports Java 8, and we do, so rather than pinning to an older version 262 * of Mockito we fake it with {@link Reflection}. This is only really possible because the thing 263 * we want to mock ({@link SerializationStreamWriter}) is an interface. Furthermore all its 264 * methods return void so we don't even need a way to specify what to return. 265 * 266 * <p>The idea is that you make an instance of this class, have the code under test call methods 267 * on it, then call {@link #verify} with a lambda that repeats the expected calls. If they match 268 * the actual calls then the test passes. 269 */ 270 private static class MickeyMouseMock<T> { 271 private boolean recording = true; 272 private final Deque<MethodCall> methodCalls = new ArrayDeque<>(); 273 private final T proxy; 274 MickeyMouseMock(Class<T> intf)275 MickeyMouseMock(Class<T> intf) { 276 this.proxy = Reflection.newProxy(intf, this::invocationHandler); 277 } 278 proxy()279 T proxy() { 280 return proxy; 281 } 282 verify(ThrowingRunnable actions)283 void verify(ThrowingRunnable actions) { 284 assertThat(recording).isTrue(); 285 recording = false; 286 try { 287 actions.run(); 288 } catch (AssertionError e) { 289 throw e; 290 } catch (Throwable t) { 291 throw new AssertionError(t); 292 } 293 assertThat(methodCalls).isEmpty(); 294 } 295 invocationHandler(Object proxy, Method method, Object[] args)296 private Object invocationHandler(Object proxy, Method method, Object[] args) { 297 if (args == null) { 298 args = new Object[0]; 299 } 300 MethodCall methodCall = MethodCall.of(method.getName(), ImmutableList.copyOf(args)); 301 if (recording) { 302 methodCalls.add(methodCall); 303 } else { // verifying 304 assertWithMessage("Missing call %s", methodCall).that(methodCalls).isNotEmpty(); 305 MethodCall recorded = methodCalls.removeFirst(); 306 assertThat(methodCall).isEqualTo(recorded); 307 } 308 return null; 309 } 310 } 311 } 312