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