1 /*
<lambda>null2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.datastore.migrations
18 
19 import android.content.Context
20 import android.content.SharedPreferences
21 import androidx.datastore.TestingSerializer
22 import androidx.datastore.core.DataMigration
23 import androidx.datastore.core.DataStore
24 import androidx.datastore.core.DataStoreFactory
25 import androidx.datastore.dataStore
26 import androidx.test.core.app.ApplicationProvider
27 import androidx.test.filters.MediumTest
28 import androidx.testutils.assertThrows
29 import com.google.common.truth.Truth.assertThat
30 import java.io.File
31 import kotlinx.coroutines.flow.first
32 import kotlinx.coroutines.runBlocking
33 import kotlinx.coroutines.test.TestScope
34 import kotlinx.coroutines.test.UnconfinedTestDispatcher
35 import kotlinx.coroutines.test.runTest
36 import org.junit.Before
37 import org.junit.Rule
38 import org.junit.Test
39 import org.junit.rules.TemporaryFolder
40 
41 private const val sharedPrefsName = "shared_prefs_name"
42 
43 private val Context.dsWithSpMigration by
44     dataStore(
45         "ds_with_sp_migration",
46         serializer = TestingSerializer(),
47         produceMigrations = { applicationContext ->
48             listOf(
49                 SharedPreferencesMigration(applicationContext, sharedPrefsName) {
50                     sharedPreferencesView: SharedPreferencesView,
51                     t: Byte ->
52                     t.plus(sharedPreferencesView.getInt("integer_key", -1)).toByte()
53                 }
54             )
55         }
56     )
57 
58 @MediumTest
59 @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
60 class SharedPreferencesMigrationTest {
61     @get:Rule val temporaryFolder = TemporaryFolder()
62 
63     private lateinit var context: Context
64     private lateinit var sharedPrefs: SharedPreferences
65     private lateinit var datastoreFile: File
66 
67     @Before
setUpnull68     fun setUp() {
69         context = ApplicationProvider.getApplicationContext()
70         sharedPrefs = context.getSharedPreferences(sharedPrefsName, Context.MODE_PRIVATE)
71         datastoreFile = temporaryFolder.newFile("test_file.preferences_pb")
72 
73         assertThat(sharedPrefs.edit().clear().commit()).isTrue()
74     }
75 
76     @Test
<lambda>null77     fun testShouldMigrateSkipsMigration() = runTest {
78         val sharedPrefsMigration =
79             SharedPreferencesMigration<Byte>(
80                 context = context,
81                 sharedPreferencesName = sharedPrefsName,
82                 shouldRunMigration = { false }
83             ) { _: SharedPreferencesView, _: Byte ->
84                 throw IllegalStateException("Migration should've been skipped.")
85             }
86 
87         val dataStore = getDataStoreWithMigrations(listOf(sharedPrefsMigration))
88 
89         // Make sure we aren't running migrate()
90         dataStore.data.first()
91     }
92 
93     @Test
<lambda>null94     fun testSharedPrefsViewContainsSpecifiedKeys() = runTest {
95         val includedKey = "key1"
96         val includedVal = 1
97         val notMigratedKey = "key2"
98 
99         assertThat(
100                 sharedPrefs
101                     .edit()
102                     .putInt(includedKey, includedVal)
103                     .putInt(notMigratedKey, 123)
104                     .commit()
105             )
106             .isTrue()
107 
108         val sharedPrefsMigration =
109             SharedPreferencesMigration(
110                 context = context,
111                 sharedPreferencesName = sharedPrefsName,
112                 keysToMigrate = setOf(includedKey)
113             ) { prefs: SharedPreferencesView, _: Byte ->
114                 assertThat(prefs.getInt(includedKey, -1)).isEqualTo(includedVal)
115                 assertThrows<IllegalStateException> { prefs.getInt(notMigratedKey, -1) }
116                 assertThat(prefs.getAll()).isEqualTo(mapOf(includedKey to includedVal))
117 
118                 99.toByte()
119             }
120 
121         val dataStore = getDataStoreWithMigrations(listOf(sharedPrefsMigration))
122 
123         assertThat(dataStore.data.first()).isEqualTo(99)
124         assertThat(sharedPrefs.contains(includedKey)).isFalse()
125         assertThat(sharedPrefs.contains(notMigratedKey)).isTrue()
126     }
127 
128     @Test
<lambda>null129     fun testSharedPrefsViewWithAllKeysSpecified() = runTest {
130         val key1 = "key1"
131         val val1 = 1
132         val key2 = "key2"
133         val val2 = 2
134 
135         assertThat(sharedPrefs.edit().putInt(key1, val1).putInt(key2, val2).commit()).isTrue()
136 
137         val sharedPrefsMigration =
138             SharedPreferencesMigration(
139                 context = context,
140                 sharedPreferencesName = sharedPrefsName
141             ) { prefs: SharedPreferencesView, _: Byte ->
142                 assertThat(prefs.getInt(key1, -1)).isEqualTo(val1)
143                 assertThat(prefs.getInt(key2, -1)).isEqualTo(val2)
144 
145                 assertThat(prefs.getAll()).isEqualTo(mapOf(key1 to val1, key2 to val2))
146 
147                 99.toByte()
148             }
149 
150         val dataStore = getDataStoreWithMigrations(listOf(sharedPrefsMigration))
151 
152         assertThat(dataStore.data.first()).isEqualTo(99)
153         assertThat(sharedPrefs.contains(key1)).isFalse()
154         assertThat(sharedPrefs.contains(key2)).isFalse()
155     }
156 
157     @Test
<lambda>null158     fun testSharedPrefsViewWithAllKeysSpecified_doesntThrowErrorWhenKeyDoesntExist() = runTest {
159         assertThat(sharedPrefs.edit().putInt("unrelated_key", -123).commit()).isTrue()
160 
161         val migration =
162             SharedPreferencesMigration(
163                 produceSharedPreferences = { sharedPrefs },
164             ) { prefs: SharedPreferencesView, _: Byte ->
165                 prefs.getInt("this_key_doesnt_exist_yet", 123).toByte()
166             }
167 
168         val dataStore = getDataStoreWithMigrations(listOf(migration))
169         assertThat(dataStore.data.first()).isEqualTo(123)
170     }
171 
172     @Test
<lambda>null173     fun producedSharedPreferencesIsUsed() = runTest {
174         assertThat(sharedPrefs.edit().putInt("integer_key", 123).commit()).isTrue()
175 
176         val migration =
177             SharedPreferencesMigration(produceSharedPreferences = { sharedPrefs }) {
178                 prefs: SharedPreferencesView,
179                 _: Byte ->
180                 assertThat(prefs.getAll().size).isEqualTo(1)
181                 assertThat(prefs.getInt("integer_key", 0)).isEqualTo(123)
182                 123
183             }
184 
185         val dataStore = getDataStoreWithMigrations(listOf(migration))
186         assertThat(dataStore.data.first()).isEqualTo(123)
187     }
188 
189     @Test
testWithTopLevelDataStoreDelegatenull190     fun testWithTopLevelDataStoreDelegate() =
191         runBlocking<Unit> {
192             File(context.filesDir, "/datastore").deleteRecursively()
193             assertThat(sharedPrefs.edit().putInt("integer_key", 123).commit()).isTrue()
194 
195             assertThat(context.dsWithSpMigration.data.first()).isEqualTo(123)
196         }
197 
getDataStoreWithMigrationsnull198     private fun getDataStoreWithMigrations(migrations: List<DataMigration<Byte>>): DataStore<Byte> {
199         return DataStoreFactory.create(
200             serializer = TestingSerializer(),
201             migrations = migrations,
202             scope = TestScope(UnconfinedTestDispatcher())
203         ) {
204             datastoreFile
205         }
206     }
207 }
208