blob: d13257743e219f03755fb646e71274dfde62db9f [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.codegentest;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static java.util.concurrent.TimeUnit.SECONDS;
import android.net.LinkAddress;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
import android.util.SparseIntArray;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Tests {@link SampleDataClass} after it's augmented with dataclass codegen.
*
* Use {@code $ . runTest.sh} to run.
*/
@RunWith(AndroidJUnit4.class)
public class SampleDataClassTest {
private SampleDataClass mSpecimen = newBuilder().build();
private static SampleDataClass.Builder newBuilder() {
return newInvalidBuilder()
.setNum(42)
.setNum2(42)
.setNum4(42)
.setName4("foobar")
.setLinkAddresses5();
}
private static SampleDataClass.Builder newInvalidBuilder() {
return new SampleDataClass.Builder(1, 2, 3, "a", 0, null)
.setName("some parcelable")
.setFlags(SampleDataClass.FLAG_MANUAL_REQUEST);
}
@Test
public void testParcelling_producesEqualInstance() {
SampleDataClass copy = parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
assertEquals(mSpecimen, copy);
assertEquals(mSpecimen.hashCode(), copy.hashCode());
}
@Test
public void testParcelling_producesInstanceWithEqualFields() {
SampleDataClass copy = parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
copy.forEachField((self, copyFieldName, copyFieldValue) -> {
mSpecimen.forEachField((self2, specimenFieldName, specimenFieldValue) -> {
if (copyFieldName.equals(specimenFieldName)
&& !copyFieldName.equals("pattern")
&& (specimenFieldValue == null
|| !specimenFieldValue.getClass().isArray())) {
assertEquals("Mismatched field values for " + copyFieldName,
specimenFieldValue, copyFieldValue);
}
});
});
}
@Test
public void testCustomParcelling_instanceIsCached() {
parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
parcelAndUnparcel(mSpecimen, SampleDataClass.CREATOR);
assertEquals(1, MyDateParcelling.sInstanceCount.get());
}
@Test
public void testDefaultFieldValue_isPropagated() {
assertEquals(new Date(42 * 42), mSpecimen.getDate());
}
@Test
public void testForEachField_avoidsBoxing() {
AtomicInteger intFieldCount = new AtomicInteger(0);
mSpecimen.forEachField(
(self, name, intValue) -> intFieldCount.getAndIncrement(),
(self, name, objectValue) -> {
if (objectValue != null) {
assertThat("Boxed field " + name,
objectValue, not(instanceOf(Integer.class)));
}
});
assertThat(intFieldCount.get(), greaterThanOrEqualTo(1));
}
@Test
public void testToString_containsEachField() {
String toString = mSpecimen.toString();
mSpecimen.forEachField((self, name, value) -> {
assertThat(toString, containsString(name));
if (value instanceof Integer) {
// Could be flags, their special toString tested separately
} else if (value instanceof Object[]) {
assertThat(toString, containsString(Arrays.toString((Object[]) value)));
} else if (value != null && value.getClass().isArray()) {
// Primitive array, uses multiple specialized Arrays.toString overloads
} else {
assertThat(toString, containsString("" + value));
}
});
}
@Test
public void testBuilder_propagatesValuesToInstance() {
assertEquals(43, newBuilder().setNum(43).build().getNum());
}
@Test
public void testPluralFields_canHaveCustomSingularBuilderName() {
newBuilder().addLinkAddress(new LinkAddress("127.0.0.1/24"));
}
@Test(expected = IllegalStateException.class)
public void testBuilder_usableOnlyOnce() {
SampleDataClass.Builder builder = newBuilder();
builder.build();
builder.build();
}
@Test(expected = IllegalStateException.class)
public void testBuilder_performsValidation() {
newInvalidBuilder().build();
}
@Test
public void testIntDefs_haveCorrectToString() {
int flagsAsInt = SampleDataClass.FLAG_MANUAL_REQUEST
| SampleDataClass.FLAG_COMPATIBILITY_MODE_REQUEST;
String flagsAsString = SampleDataClass.requestFlagsToString(flagsAsInt);
assertThat(flagsAsString, containsString("MANUAL_REQUEST"));
assertThat(flagsAsString, containsString("COMPATIBILITY_MODE_REQUEST"));
assertThat(flagsAsString, not(containsString("1")));
assertThat(flagsAsString, not(containsString("" + flagsAsInt)));
String dataclassToString = newBuilder()
.setFlags(flagsAsInt)
.setState(SampleDataClass.STATE_UNDEFINED)
.build()
.toString();
assertThat(dataclassToString, containsString(flagsAsString));
assertThat(dataclassToString, containsString("UNDEFINED"));
}
@Test(expected = IllegalArgumentException.class)
public void testFlags_getValidated() {
newBuilder().setFlags(12345).build();
}
@Test(expected = IllegalArgumentException.class)
public void testIntEnums_getValidated() {
newBuilder().setState(12345).build();
}
@Test(expected = IllegalArgumentException.class)
public void testStringEnums_getValidated() {
newBuilder().setStateName("foo").build();
}
@Test(expected = IllegalStateException.class)
public void testCustomValidation_isTriggered() {
newBuilder().setNum2(-1).setNum4(1).build();
}
@Test
public void testLazyInit_isLazilyCalledOnce() {
assertNull(mSpecimen.mTmpStorage);
int[] tmpStorage = mSpecimen.getTmpStorage();
assertNotNull(tmpStorage);
assertSame(tmpStorage, mSpecimen.mTmpStorage);
int[] tmpStorageAgain = mSpecimen.getTmpStorage();
assertSame(tmpStorage, tmpStorageAgain);
}
@Test(expected = IllegalStateException.class)
public void testCustomAnnotationValidation_isRun() {
newBuilder().setDayOfWeek(42).build();
}
@Test
public void testDataStructures_parcelCorrectly() {
SampleWithCustomBuilder otherParcelable = new SampleWithCustomBuilder.Builder().setDelay(3, SECONDS).build();
ParcelAllTheThingsDataClass instance = new ParcelAllTheThingsDataClass.Builder()
.setIntArray(40, 41)
.addMap("foo", otherParcelable)
.setSparseArray(new SparseArray<SampleWithCustomBuilder>() {{
put(45, otherParcelable);
}})
.setSparseIntArray(new SparseIntArray() {{
put(48, 49);
}})
.addStringMap("foo2", "fooValue")
.setStringArray("foo", "bar")
.addStringList("foo")
.build();
ParcelAllTheThingsDataClass unparceledInstance =
parcelAndUnparcel(instance, ParcelAllTheThingsDataClass.CREATOR);
// SparseArray and friends don't implement equals
// so just compare string representations instead
assertEquals(instance.toString(), unparceledInstance.toString());
}
@Test
public void testNestedDataClasses_notMangledWhenParceled() {
assertEqualsAfterParcelling(
new SampleWithNestedDataClasses.NestedDataClass("1"),
SampleWithNestedDataClasses.NestedDataClass.CREATOR);
assertEqualsAfterParcelling(
new SampleWithNestedDataClasses.NestedDataClass2("2"),
SampleWithNestedDataClasses.NestedDataClass2.CREATOR);
assertEqualsAfterParcelling(
new SampleWithNestedDataClasses.NestedDataClass2.NestedDataClass3(3),
SampleWithNestedDataClasses.NestedDataClass2.NestedDataClass3.CREATOR);
}
private static <T extends Parcelable> void assertEqualsAfterParcelling(
T p, Parcelable.Creator<T> creator) {
assertEquals(p, parcelAndUnparcel(p, creator));
}
private static <T extends Parcelable> T parcelAndUnparcel(
T original, Parcelable.Creator<T> creator) {
Parcel p = Parcel.obtain();
try {
original.writeToParcel(p, 0);
p.setDataPosition(0);
return creator.createFromParcel(p);
} finally {
p.recycle();
}
}
}