From c869403b45613c9da91ec40449ec652d3091cab8 Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Thu, 4 Aug 2022 09:37:39 -0400 Subject: [PATCH] Add a FakeSharedPreferences for testing * apply and commit both act in the same way, applying the contents of the Editor synchronously * data is stored in memory in the instance * listeners are dispatched synchronously on the same thread that calls apply/commit Test: atest FakeSharedPreferencesTest Bug: 180126509 Change-Id: Ie443f6269a9aa6d699631d8524bfa9fe6b3474e4 --- .../util/FakeSharedPreferencesTest.kt | 259 ++++++++++++++++++ .../systemui/util/FakeSharedPreferences.kt | 164 +++++++++++ 2 files changed, 423 insertions(+) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt new file mode 100644 index 0000000000000..d886ffdf0e585 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt @@ -0,0 +1,259 @@ +/* + * 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 com.android.systemui.util + +import android.content.SharedPreferences +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.eq +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyString +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class FakeSharedPreferencesTest : SysuiTestCase() { + + @Mock + private lateinit var listener: SharedPreferences.OnSharedPreferenceChangeListener + + private lateinit var sharedPreferences: SharedPreferences + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + sharedPreferences = FakeSharedPreferences() + } + + @Test + fun testGetString_default() { + val default = "default" + val result = sharedPreferences.getString("key", default) + assertThat(result).isEqualTo(default) + } + + @Test + fun testGetStringSet_default() { + val default = setOf("one", "two") + val result = sharedPreferences.getStringSet("key", default) + assertThat(result).isEqualTo(default) + } + + @Test + fun testGetInt_default() { + val default = 10 + val result = sharedPreferences.getInt("key", default) + assertThat(result).isEqualTo(default) + } + + @Test + fun testGetLong_default() { + val default = 11L + val result = sharedPreferences.getLong("key", default) + assertThat(result).isEqualTo(default) + } + + @Test + fun testGetFloat_default() { + val default = 1.3f + val result = sharedPreferences.getFloat("key", default) + assertThat(result).isEqualTo(default) + } + + @Test + fun testGetBoolean_default() { + val default = true + val result = sharedPreferences.getBoolean("key", default) + assertThat(result).isEqualTo(default) + } + + @Test + fun testPutValuesAndRetrieve() { + val editor = sharedPreferences.edit() + val data = listOf>( + Data( + "keyString", + "value", + SharedPreferences.Editor::putString, + { getString(it, "") } + ), + Data( + "keyStringSet", + setOf("one", "two"), + SharedPreferences.Editor::putStringSet, + { getStringSet(it, emptySet()) } + ), + Data("keyInt", 10, SharedPreferences.Editor::putInt, { getInt(it, 0) }), + Data("keyLong", 11L, SharedPreferences.Editor::putLong, { getLong(it, 0L) }), + Data( + "keyFloat", + 1.3f, + SharedPreferences.Editor::putFloat, + { getFloat(it, 0f) } + ), + Data( + "keyBoolean", + true, + SharedPreferences.Editor::putBoolean, + { getBoolean(it, false) } + ) + ) + + data.fold(editor) { ed, d -> + d.set(ed) + } + editor.commit() + + data.forEach { + assertThat(it.get(sharedPreferences)).isEqualTo(it.value) + } + } + + @Test + fun testContains() { + sharedPreferences.edit().putInt("key", 10).commit() + + assertThat(sharedPreferences.contains("key")).isTrue() + assertThat(sharedPreferences.contains("other")).isFalse() + } + + @Test + fun testOverwrite() { + sharedPreferences.edit().putInt("key", 10).commit() + sharedPreferences.edit().putInt("key", 11).commit() + + assertThat(sharedPreferences.getInt("key", 0)).isEqualTo(11) + } + + @Test + fun testDeleteString() { + sharedPreferences.edit().putString("key", "value").commit() + sharedPreferences.edit().putString("key", null).commit() + + assertThat(sharedPreferences.contains("key")).isFalse() + } + + @Test + fun testDeleteAndReplaceString() { + sharedPreferences.edit().putString("key", "value").commit() + sharedPreferences.edit().putString("key", "other").putString("key", null).commit() + + assertThat(sharedPreferences.getString("key", "")).isEqualTo("other") + } + + @Test + fun testDeleteStringSet() { + sharedPreferences.edit().putStringSet("key", setOf("one")).commit() + sharedPreferences.edit().putStringSet("key", setOf("two")).commit() + + assertThat(sharedPreferences.getStringSet("key", emptySet())).isEqualTo(setOf("two")) + } + + @Test + fun testClear() { + sharedPreferences.edit().putInt("keyInt", 1).putString("keyString", "a").commit() + sharedPreferences.edit().clear().commit() + + assertThat(sharedPreferences.contains("keyInt")).isFalse() + assertThat(sharedPreferences.contains("keyString")).isFalse() + } + + @Test + fun testClearAndWrite() { + sharedPreferences.edit().putInt("key", 10).commit() + sharedPreferences.edit().putInt("key", 11).clear().commit() + + assertThat(sharedPreferences.getInt("key", 0)).isEqualTo(11) + } + + @Test + fun testListenerNotifiedOnChanges() { + sharedPreferences.registerOnSharedPreferenceChangeListener(listener) + + sharedPreferences.edit().putInt("keyInt", 10).putString("keyString", "value").commit() + + verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyInt") + verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyString") + verifyNoMoreInteractions(listener) + } + + @Test + fun testListenerNotifiedOnClear() { + sharedPreferences.edit().putInt("keyInt", 10).commit() + sharedPreferences.registerOnSharedPreferenceChangeListener(listener) + + sharedPreferences.edit().clear().commit() + + verify(listener).onSharedPreferenceChanged(sharedPreferences, null) + verifyNoMoreInteractions(listener) + } + + @Test + fun testListenerNotifiedOnRemoval() { + sharedPreferences.edit() + .putString("keyString", "a") + .putStringSet("keySet", setOf("a")) + .commit() + + sharedPreferences.registerOnSharedPreferenceChangeListener(listener) + sharedPreferences.edit().putString("keyString", null).putStringSet("keySet", null).commit() + + verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyString") + verify(listener).onSharedPreferenceChanged(sharedPreferences, "keySet") + verifyNoMoreInteractions(listener) + } + + @Test + fun testListenerUnregistered() { + sharedPreferences.registerOnSharedPreferenceChangeListener(listener) + sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener) + sharedPreferences.edit().putInt("key", 10).commit() + + verify(listener, never()).onSharedPreferenceChanged(eq(sharedPreferences), anyString()) + } + + @Test + fun testSharedPreferencesOnlyModifiedOnCommit() { + sharedPreferences.edit().putInt("key", 10) + + assertThat(sharedPreferences.contains("key")).isFalse() + } + + private data class Data( + val key: String, + val value: T, + private val setter: SharedPreferences.Editor.(String, T) -> SharedPreferences.Editor, + private val getter: SharedPreferences.(String) -> T + ) { + fun set(editor: SharedPreferences.Editor): SharedPreferences.Editor { + return editor.setter(key, value) + } + + fun get(sharedPreferences: SharedPreferences): T { + return sharedPreferences.getter(key) + } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt new file mode 100644 index 0000000000000..4a881a7ce8987 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt @@ -0,0 +1,164 @@ +/* + * 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 com.android.systemui.util + +import android.content.SharedPreferences + +/** + * Fake [SharedPreferences] to use within tests + * + * This will act in the same way as a real one for a particular file, but will store all the + * data in memory in the instance. + * + * [SharedPreferences.Editor.apply] and [SharedPreferences.Editor.commit] both act in the same way, + * synchronously modifying the stored data. Listeners are dispatched in the same thread, also + * synchronously. + */ +class FakeSharedPreferences : SharedPreferences { + private val data = mutableMapOf() + private val listeners = mutableSetOf() + + override fun getAll(): Map { + return data + } + + override fun getString(key: String, defValue: String?): String? { + return data.getOrDefault(key, defValue) as? String? + } + + override fun getStringSet(key: String, defValues: MutableSet?): MutableSet? { + return data.getOrDefault(key, defValues) as? MutableSet? + } + + override fun getInt(key: String, defValue: Int): Int { + return data.getOrDefault(key, defValue) as Int + } + + override fun getLong(key: String, defValue: Long): Long { + return data.getOrDefault(key, defValue) as Long + } + + override fun getFloat(key: String, defValue: Float): Float { + return data.getOrDefault(key, defValue) as Float + } + + override fun getBoolean(key: String, defValue: Boolean): Boolean { + return data.getOrDefault(key, defValue) as Boolean + } + + override fun contains(key: String): Boolean { + return key in data + } + + override fun edit(): SharedPreferences.Editor { + return Editor() + } + + override fun registerOnSharedPreferenceChangeListener( + listener: SharedPreferences.OnSharedPreferenceChangeListener + ) { + listeners.add(listener) + } + + override fun unregisterOnSharedPreferenceChangeListener( + listener: SharedPreferences.OnSharedPreferenceChangeListener + ) { + listeners.remove(listener) + } + + private inner class Editor : SharedPreferences.Editor { + + private var clear = false + private val changes = mutableMapOf() + private val removals = mutableSetOf() + + override fun putString(key: String, value: String?): SharedPreferences.Editor { + if (value != null) { + changes[key] = value + } else { + removals.add(key) + } + return this + } + + override fun putStringSet( + key: String, + values: MutableSet? + ): SharedPreferences.Editor { + if (values != null) { + changes[key] = values + } else { + removals.add(key) + } + return this + } + + override fun putInt(key: String, value: Int): SharedPreferences.Editor { + changes[key] = value + return this + } + + override fun putLong(key: String, value: Long): SharedPreferences.Editor { + changes[key] = value + return this + } + + override fun putFloat(key: String, value: Float): SharedPreferences.Editor { + changes[key] = value + return this + } + + override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor { + changes[key] = value + return this + } + + override fun remove(key: String): SharedPreferences.Editor { + removals.add(key) + return this + } + + override fun clear(): SharedPreferences.Editor { + clear = true + return this + } + + override fun commit(): Boolean { + if (clear) { + data.clear() + } + removals.forEach { data.remove(it) } + data.putAll(changes) + val keys = removals + data.keys + if (clear || removals.isNotEmpty() || data.isNotEmpty()) { + listeners.forEach { listener -> + if (clear) { + listener.onSharedPreferenceChanged(this@FakeSharedPreferences, null) + } + keys.forEach { + listener.onSharedPreferenceChanged(this@FakeSharedPreferences, it) + } + } + } + return true + } + + override fun apply() { + commit() + } + } +}