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

Commit 93f1e82a authored by Jacky Wang's avatar Jacky Wang Committed by Cherrypicker Worker
Browse files

[DataStore] Add UT for Observer

Bug: 325144964
Test: atest SettingsLibDataStoreTest --iterations 1000
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:f5eeba5011410f07fe6efe5c2900f16ca13e9e30)
Merged-In: I688d58c27265829b29d65545238cb2d03619acb6
Change-Id: I688d58c27265829b29d65545238cb2d03619acb6
parent 1745193e
Loading
Loading
Loading
Loading
+24 −0
Original line number Original line Diff line number Diff line
package {
    default_applicable_licenses: ["frameworks_base_license"],
}

android_app {
    name: "SettingsLibDataStoreShell",
    platform_apis: true,
}

android_robolectric_test {
    name: "SettingsLibDataStoreTest",
    srcs: ["src/**/*"],
    static_libs: [
        "SettingsLibDataStore",
        "androidx.test.ext.junit",
        "guava",
        "mockito-robolectric-prebuilt", // mockito deps order matters!
        "mockito-kotlin2",
    ],
    java_resource_dirs: ["config"],
    instrumentation_for: "SettingsLibDataStoreShell",
    coverage_libs: ["SettingsLibDataStore"],
    upstream: true,
}
+6 −0
Original line number Original line Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.settingslib.datastore.test">

    <application android:debuggable="true" />
</manifest>
+1 −0
Original line number Original line Diff line number Diff line
sdk=NEWEST_SDK
+109 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.settingslib.datastore

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicInteger
import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.any
import org.mockito.kotlin.never
import org.mockito.kotlin.reset
import org.mockito.kotlin.verify

@RunWith(AndroidJUnit4::class)
class ObserverTest {
    @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()

    @Mock private lateinit var observer1: Observer

    @Mock private lateinit var observer2: Observer

    @Mock private lateinit var executor: Executor

    private val observable = DataObservable()

    @Test
    fun addObserver_sameExecutor() {
        observable.addObserver(observer1, executor)
        observable.addObserver(observer1, executor)
    }

    @Test
    fun addObserver_differentExecutor() {
        observable.addObserver(observer1, executor)
        assertThrows(IllegalStateException::class.java) {
            observable.addObserver(observer1, MoreExecutors.directExecutor())
        }
    }

    @Test
    fun addObserver_weaklyReferenced() {
        val counter = AtomicInteger()
        var observer: Observer? = Observer { counter.incrementAndGet() }
        observable.addObserver(observer!!, MoreExecutors.directExecutor())

        observable.notifyChange(ChangeReason.UPDATE)
        assertThat(counter.get()).isEqualTo(1)

        // trigger GC, the observer callback should not be invoked
        @Suppress("unused")
        observer = null
        System.gc()
        System.runFinalization()

        observable.notifyChange(ChangeReason.UPDATE)
        assertThat(counter.get()).isEqualTo(1)
    }

    @Test
    fun addObserver_notifyObservers_removeObserver() {
        observable.addObserver(observer1, MoreExecutors.directExecutor())
        observable.addObserver(observer2, executor)

        observable.notifyChange(ChangeReason.DELETE)

        verify(observer1).onChanged(ChangeReason.DELETE)
        verify(observer2, never()).onChanged(any())
        verify(executor).execute(any())

        reset(observer1, executor)
        observable.removeObserver(observer2)

        observable.notifyChange(ChangeReason.UPDATE)
        verify(observer1).onChanged(ChangeReason.UPDATE)
        verify(executor, never()).execute(any())
    }

    @Test
    fun notifyChange_addObserverWithinCallback() {
        // ConcurrentModificationException is raised if it is not implemented correctly
        observable.addObserver(
            { observable.addObserver(observer1, executor) },
            MoreExecutors.directExecutor()
        )
        observable.notifyChange(ChangeReason.UPDATE)
    }
}