diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index 22ddbccbaaaed..c70f1f07e7398 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -57,6 +57,18 @@ { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } ] }, + { + "file_patterns": [ + "Parcel\\.java", + "[^/]*Bundle[^/]*\\.java" + ], + "name": "FrameworksMockingCoreTests", + "options": [ + { "include-filter": "android.os.BundleRecyclingTest"}, + { "exclude-annotation": "androidx.test.filters.FlakyTest" }, + { "exclude-annotation": "org.junit.Ignore" } + ] + }, { "file_patterns": [ "BatteryUsageStats[^/]*\\.java", diff --git a/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java b/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java new file mode 100644 index 0000000000000..7c76498138241 --- /dev/null +++ b/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2022 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 android.os; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.dx.mockito.inline.extended.StaticMockitoSession; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Test for verifying {@link android.os.Bundle} recycles the underlying parcel where needed. + * + *
Build/Install/Run:
+ * atest FrameworksMockingCoreTests:android.os.BundleRecyclingTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class BundleRecyclingTest {
+ private Parcel mParcelSpy;
+ private Bundle mBundle;
+
+ @Before
+ public void setUp() throws Exception {
+ setUpBundle(/* hasLazy */ true);
+ }
+
+ @Test
+ public void bundleClear_whenUnparcelledWithoutLazy_recyclesParcelOnce() {
+ setUpBundle(/* hasLazy */ false);
+ // Will unparcel and immediately recycle parcel
+ assertNotNull(mBundle.getString("key"));
+ verify(mParcelSpy, times(1)).recycle();
+ assertFalse(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+ }
+
+ @Test
+ public void bundleClear_whenParcelled_recyclesParcel() {
+ assertTrue(mBundle.isParcelled());
+ verify(mParcelSpy, times(0)).recycle();
+
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenUnparcelledWithLazyValueUnwrapped_recyclesParcel() {
+ // Will unparcel with a lazy value, and immediately unwrap the lazy value,
+ // with no lazy values left at the end of getParcelable
+ assertNotNull(mBundle.getParcelable("key", CustomParcelable.class));
+ verify(mParcelSpy, times(0)).recycle();
+
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenUnparcelledWithLazy_recyclesParcel() {
+ // Will unparcel but keep the CustomParcelable lazy
+ assertFalse(mBundle.isEmpty());
+ verify(mParcelSpy, times(0)).recycle();
+
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenClearedWithSharedParcel_doesNotRecycleParcel() {
+ Bundle copy = new Bundle();
+ copy.putAll(mBundle);
+
+ mBundle.clear();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ copy.clear();
+ assertTrue(copy.isDefinitelyEmpty());
+
+ verify(mParcelSpy, never()).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenClearedWithCopiedParcel_doesNotRecycleParcel() {
+ // Will unparcel but keep the CustomParcelable lazy
+ assertFalse(mBundle.isEmpty());
+
+ Bundle copy = mBundle.deepCopy();
+ copy.putAll(mBundle);
+
+ mBundle.clear();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ copy.clear();
+ assertTrue(copy.isDefinitelyEmpty());
+
+ verify(mParcelSpy, never()).recycle();
+ }
+
+ private void setUpBundle(boolean hasLazy) {
+ AtomicReference