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

Commit f5eeba50 authored by Jacky Wang's avatar Jacky Wang
Browse files

[DataStore] Add UT for Observer

Bug: 325144964
Test: atest SettingsLibDataStoreTest --iterations 1000
Change-Id: I688d58c27265829b29d65545238cb2d03619acb6
parent 50981b6c
Loading
Loading
Loading
Loading
+24 −0
Original line number 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 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 Diff line number Diff line
sdk=NEWEST_SDK
+109 −0
Original line number 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)
    }
}