1 /*
2  * Copyright (C) 2019 The Android Open Source Project
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.android.codegentest;
17 
18 import static org.hamcrest.CoreMatchers.containsString;
19 import static org.hamcrest.CoreMatchers.instanceOf;
20 import static org.hamcrest.CoreMatchers.not;
21 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertNotNull;
24 import static org.junit.Assert.assertNull;
25 import static org.junit.Assert.assertSame;
26 import static org.junit.Assert.assertThat;
27 
28 import static java.util.concurrent.TimeUnit.SECONDS;
29 
30 import android.net.LinkAddress;
31 import android.os.Bundle;
32 import android.os.Parcel;
33 import android.os.Parcelable;
34 import android.util.SparseArray;
35 import android.util.SparseIntArray;
36 
37 import androidx.test.runner.AndroidJUnit4;
38 
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 
42 import java.util.Arrays;
43 import java.util.Date;
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.concurrent.atomic.AtomicInteger;
48 
49 /**
50  * Tests {@link SampleDataClass} after it's augmented with dataclass codegen.
51  *
52  * Use {@code $ . runTest.sh} to run.
53  */
54 @RunWith(AndroidJUnit4.class)
55 public class SampleDataClassTest {
56 
57     private SampleDataClass mSpecimen = newBuilder().build();
58 
newBuilder()59     private static SampleDataClass.Builder newBuilder() {
60         return newInvalidBuilder()
61                 .setNum(42)
62                 .setNum2(42)
63                 .setNum4(42)
64                 .setName4("foobar")
65                 .setLinkAddresses5();
66     }
67 
newInvalidBuilder()68     private static SampleDataClass.Builder newInvalidBuilder() {
69         return new SampleDataClass.Builder(1, 2, 3, "a", 0, null)
70                 .setName("some parcelable")
71                 .setFlags(SampleDataClass.FLAG_MANUAL_REQUEST);
72     }
73 
74     @Test
testParcelling_producesEqualInstance()75     public void testParcelling_producesEqualInstance() {
76         SampleDataClass copy = parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
77         assertEquals(mSpecimen, copy);
78         assertEquals(mSpecimen.hashCode(), copy.hashCode());
79     }
80 
81     @Test
testParcelling_producesInstanceWithEqualFields()82     public void testParcelling_producesInstanceWithEqualFields() {
83         SampleDataClass copy = parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
84         copy.forEachField((self, copyFieldName, copyFieldValue) -> {
85             mSpecimen.forEachField((self2, specimenFieldName, specimenFieldValue) -> {
86                 if (copyFieldName.equals(specimenFieldName)
87                         && !copyFieldName.equals("pattern")
88                         && (specimenFieldValue == null
89                                 || !specimenFieldValue.getClass().isArray())) {
90                     assertEquals("Mismatched field values for " + copyFieldName,
91                             specimenFieldValue, copyFieldValue);
92                 }
93             });
94         });
95     }
96 
97     @Test
testCustomParcelling_instanceIsCached()98     public void testCustomParcelling_instanceIsCached() {
99         parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
100         parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
101         assertEquals(1, MyDateParcelling.sInstanceCount.get());
102     }
103 
104     @Test
testDefaultFieldValue_isPropagated()105     public void testDefaultFieldValue_isPropagated() {
106         assertEquals(new Date(42 * 42), mSpecimen.getDate());
107     }
108 
109     @Test
testForEachField_avoidsBoxing()110     public void testForEachField_avoidsBoxing() {
111         AtomicInteger intFieldCount = new AtomicInteger(0);
112         mSpecimen.forEachField(
113                 (self, name, intValue) -> intFieldCount.getAndIncrement(),
114                 (self, name, objectValue) -> {
115                     if (objectValue != null) {
116                         assertThat("Boxed field " + name,
117                                 objectValue, not(instanceOf(Integer.class)));
118                     }
119                 });
120         assertThat(intFieldCount.get(), greaterThanOrEqualTo(1));
121     }
122 
123     @Test
testToString_containsEachField()124     public void testToString_containsEachField() {
125         String toString = mSpecimen.toString();
126 
127         mSpecimen.forEachField((self, name, value) -> {
128             assertThat(toString, containsString(name));
129             if (value instanceof Integer) {
130                 // Could be flags, their special toString tested separately
131             } else if (value instanceof Object[]) {
132                 assertThat(toString, containsString(Arrays.toString((Object[]) value)));
133             } else if (value != null && value.getClass().isArray()) {
134                 // Primitive array, uses multiple specialized Arrays.toString overloads
135             } else {
136                 assertThat(toString, containsString("" + value));
137             }
138         });
139     }
140 
141     @Test
testBuilder_propagatesValuesToInstance()142     public void testBuilder_propagatesValuesToInstance() {
143         assertEquals(43, newBuilder().setNum(43).build().getNum());
144     }
145 
146     @Test
testPluralFields_canHaveCustomSingularBuilderName()147     public void testPluralFields_canHaveCustomSingularBuilderName() {
148         newBuilder().addLinkAddress(new LinkAddress("127.0.0.1/24"));
149     }
150 
151     @Test(expected = IllegalStateException.class)
testBuilder_usableOnlyOnce()152     public void testBuilder_usableOnlyOnce() {
153         SampleDataClass.Builder builder = newBuilder();
154         builder.build();
155         builder.build();
156     }
157 
158     @Test(expected = IllegalStateException.class)
testBuilder_performsValidation()159     public void testBuilder_performsValidation() {
160         newInvalidBuilder().build();
161     }
162 
163     @Test
testIntDefs_haveCorrectToString()164     public void testIntDefs_haveCorrectToString() {
165         int flagsAsInt = SampleDataClass.FLAG_MANUAL_REQUEST
166                 | SampleDataClass.FLAG_COMPATIBILITY_MODE_REQUEST;
167         String flagsAsString = SampleDataClass.requestFlagsToString(flagsAsInt);
168 
169         assertThat(flagsAsString, containsString("MANUAL_REQUEST"));
170         assertThat(flagsAsString, containsString("COMPATIBILITY_MODE_REQUEST"));
171         assertThat(flagsAsString, not(containsString("1")));
172         assertThat(flagsAsString, not(containsString("" + flagsAsInt)));
173 
174         String dataclassToString = newBuilder()
175                 .setFlags(flagsAsInt)
176                 .setState(SampleDataClass.STATE_UNDEFINED)
177                 .build()
178                 .toString();
179         assertThat(dataclassToString, containsString(flagsAsString));
180         assertThat(dataclassToString, containsString("UNDEFINED"));
181     }
182 
183     @Test(expected = IllegalArgumentException.class)
testFlags_getValidated()184     public void testFlags_getValidated() {
185         newBuilder().setFlags(12345).build();
186     }
187 
188     @Test(expected = IllegalArgumentException.class)
testIntEnums_getValidated()189     public void testIntEnums_getValidated() {
190         newBuilder().setState(12345).build();
191     }
192 
193     @Test(expected = IllegalArgumentException.class)
testStringEnums_getValidated()194     public void testStringEnums_getValidated() {
195         newBuilder().setStateName("foo").build();
196     }
197 
198     @Test(expected = IllegalStateException.class)
testCustomValidation_isTriggered()199     public void testCustomValidation_isTriggered() {
200         newBuilder().setNum2(-1).setNum4(1).build();
201     }
202 
203     @Test
testLazyInit_isLazilyCalledOnce()204     public void testLazyInit_isLazilyCalledOnce() {
205         assertNull(mSpecimen.mTmpStorage);
206 
207         int[] tmpStorage = mSpecimen.getTmpStorage();
208         assertNotNull(tmpStorage);
209         assertSame(tmpStorage, mSpecimen.mTmpStorage);
210 
211         int[] tmpStorageAgain = mSpecimen.getTmpStorage();
212         assertSame(tmpStorage, tmpStorageAgain);
213     }
214 
215     @Test(expected = IllegalStateException.class)
testCustomAnnotationValidation_isRun()216     public void testCustomAnnotationValidation_isRun() {
217         newBuilder().setDayOfWeek(42).build();
218     }
219 
220     @Test
testDataStructures_parcelCorrectly()221     public void testDataStructures_parcelCorrectly() {
222         SampleWithCustomBuilder otherParcelable = new SampleWithCustomBuilder.Builder().setDelay(3, SECONDS).build();
223 
224         ParcelAllTheThingsDataClass instance = new ParcelAllTheThingsDataClass.Builder()
225                 .setIntArray(40, 41)
226                 .addMap("foo", otherParcelable)
227                 .setSparseArray(new SparseArray<SampleWithCustomBuilder>() {{
228                     put(45, otherParcelable);
229                 }})
230                 .setSparseIntArray(new SparseIntArray() {{
231                     put(48, 49);
232                 }})
233                 .addStringMap("foo2", "fooValue")
234                 .setStringArray("foo", "bar")
235                 .addStringList("foo")
236                 .build();
237 
238         ParcelAllTheThingsDataClass unparceledInstance =
239                 parcelAndUnparcel(instance, ParcelAllTheThingsDataClass.CREATOR);
240 
241         // SparseArray and friends don't implement equals
242         // so just compare string representations instead
243         assertEquals(instance.toString(), unparceledInstance.toString());
244     }
245 
246     @Test
testNestedDataClasses_notMangledWhenParceled()247     public void testNestedDataClasses_notMangledWhenParceled() {
248         assertEqualsAfterParcelling(
249                 new SampleWithNestedDataClasses.NestedDataClass("1"),
250                 SampleWithNestedDataClasses.NestedDataClass.CREATOR);
251 
252         assertEqualsAfterParcelling(
253                 new SampleWithNestedDataClasses.NestedDataClass2("2"),
254                 SampleWithNestedDataClasses.NestedDataClass2.CREATOR);
255 
256         assertEqualsAfterParcelling(
257                 new SampleWithNestedDataClasses.NestedDataClass2.NestedDataClass3(3),
258                 SampleWithNestedDataClasses.NestedDataClass2.NestedDataClass3.CREATOR);
259     }
260 
assertEqualsAfterParcelling( T p, Parcelable.Creator<T> creator)261     private static <T extends Parcelable> void assertEqualsAfterParcelling(
262             T p, Parcelable.Creator<T> creator) {
263         assertEquals(p, parcelAndUnparcel(p, creator));
264     }
265 
parcelAndUnparcel( T original, Parcelable.Creator<T> creator)266     private static <T extends Parcelable> T parcelAndUnparcel(
267             T original, Parcelable.Creator<T> creator) {
268         Parcel p = Parcel.obtain();
269         try {
270             original.writeToParcel(p, 0);
271             p.setDataPosition(0);
272             return creator.createFromParcel(p);
273         } finally {
274             p.recycle();
275         }
276     }
277 }
278