Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bfa7ecc2 authored by Hani Kazmi's avatar Hani Kazmi Committed by Automerger Merge Worker
Browse files

Merge "BaseBundle.java: Adding tests for 'Recycle underlying parcel for...

Merge "BaseBundle.java: Adding tests for 'Recycle underlying parcel for bundle'" into tm-dev am: 6b07af5e

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18929334



Change-Id: I5f81f71ccdd368a37653e31399d435570870350f
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 0ee5cd13 6b07af5e
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -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",
+256 −0
Original line number Diff line number Diff line
/*
 * 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.
 *
 * <p>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<Parcel> parcel = new AtomicReference<>();
        StaticMockitoSession session = mockitoSession()
                .strictness(Strictness.STRICT_STUBS)
                .spyStatic(Parcel.class)
                .startMocking();
        doAnswer((Answer<Parcel>) invocationOnSpy -> {
            Parcel spy = (Parcel) invocationOnSpy.callRealMethod();
            spyOn(spy);
            parcel.set(spy);
            return spy;
        }).when(() -> Parcel.obtain());

        Bundle bundle = new Bundle();
        bundle.setClassLoader(getClass().getClassLoader());
        Parcel p = createBundle(hasLazy);
        bundle.readFromParcel(p);
        p.recycle();

        session.finishMocking();

        mParcelSpy = parcel.get();
        mBundle = bundle;
    }

    /**
     * Create a test bundle, parcel it and return the parcel.
     */
    private Parcel createBundle(boolean hasLazy) {
        final Bundle source = new Bundle();
        if (hasLazy) {
            source.putParcelable("key", new CustomParcelable(13, "Tiramisu"));
        } else {
            source.putString("key", "tiramisu");
        }
        return getParcelledBundle(source);
    }

    /**
     * Take a bundle, write it to a parcel and return the parcel.
     */
    private Parcel getParcelledBundle(Bundle bundle) {
        final Parcel p = Parcel.obtain();
        // Don't use p.writeParcelabe(), which would write the creator, which we don't need.
        bundle.writeToParcel(p, 0);
        p.setDataPosition(0);
        return p;
    }

    private static class CustomParcelable implements Parcelable {
        public final int integer;
        public final String string;

        CustomParcelable(int integer, String string) {
            this.integer = integer;
            this.string = string;
        }

        protected CustomParcelable(Parcel in) {
            integer = in.readInt();
            string = in.readString();
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            out.writeInt(integer);
            out.writeString(string);
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }
            if (!(other instanceof CustomParcelable)) {
                return false;
            }
            CustomParcelable that = (CustomParcelable) other;
            return integer == that.integer && string.equals(that.string);
        }

        @Override
        public int hashCode() {
            return Objects.hash(integer, string);
        }

        public static final Creator<CustomParcelable> CREATOR = new Creator<CustomParcelable>() {
            @Override
            public CustomParcelable createFromParcel(Parcel in) {
                return new CustomParcelable(in);
            }
            @Override
            public CustomParcelable[] newArray(int size) {
                return new CustomParcelable[size];
            }
        };
    }
}