| /* |
| * Copyright 2022 Google LLC |
| * |
| * 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.google.android.libraries.mobiledatadownload.internal; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| |
| import android.content.Context; |
| import android.content.SharedPreferences; |
| import android.util.Pair; |
| import androidx.test.core.app.ApplicationProvider; |
| import com.google.android.libraries.mobiledatadownload.SilentFeedback; |
| import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; |
| import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend; |
| import com.google.android.libraries.mobiledatadownload.internal.logging.EventLogger; |
| import com.google.android.libraries.mobiledatadownload.internal.util.DirectoryUtil; |
| import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupUtil; |
| import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupsMetadataUtil; |
| import com.google.android.libraries.mobiledatadownload.internal.util.ProtoConversionUtil; |
| import com.google.android.libraries.mobiledatadownload.internal.util.SharedPreferencesUtil; |
| import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource; |
| import com.google.android.libraries.mobiledatadownload.testing.TestFlags; |
| import com.google.common.base.Optional; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.util.concurrent.MoreExecutors; |
| import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup; |
| import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; |
| import com.google.mobiledatadownload.internal.MetadataProto.GroupKey; |
| import com.google.mobiledatadownload.internal.MetadataProto.GroupKeyProperties; |
| import java.io.File; |
| import java.time.Duration; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.Executors; |
| import org.junit.After; |
| import org.junit.Before; |
| 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.robolectric.ParameterizedRobolectricTestRunner; |
| import org.robolectric.ParameterizedRobolectricTestRunner.Parameter; |
| import org.robolectric.ParameterizedRobolectricTestRunner.Parameters; |
| |
| @RunWith(ParameterizedRobolectricTestRunner.class) |
| public class FileGroupsMetadataTest { |
| |
| // TODO(b/26110951): use Parameterized runner once android_test supports it |
| private enum MetadataStoreImpl { |
| SP_IMPL, |
| } |
| |
| // Whether to use PDS metadata store or SharedPreferences metadata store. |
| @Parameter(value = 0) |
| public MetadataStoreImpl metadataStoreImpl; |
| |
| @Parameter(value = 1) |
| public Optional<String> instanceId; |
| |
| @Parameters(name = "metadataStoreImpl = {0} instanceId = {1}") |
| public static Collection<Object[]> parameters() { |
| return Arrays.asList( |
| new Object[][] { |
| {MetadataStoreImpl.SP_IMPL, Optional.absent()}, |
| {MetadataStoreImpl.SP_IMPL, Optional.of("id")}, |
| }); |
| } |
| |
| private static final String TEST_GROUP = "test-group"; |
| private static final String TEST_GROUP_2 = "test-group-2"; |
| private static final String TEST_GROUP_3 = "test-group-3"; |
| private static final Executor CONTROL_EXECUTOR = |
| MoreExecutors.newSequentialExecutor(Executors.newCachedThreadPool()); |
| |
| private static GroupKey testKey; |
| private static GroupKey testKey2; |
| private static GroupKey testKey3; |
| |
| private SynchronousFileStorage fileStorage; |
| private Context context; |
| private FakeTimeSource testClock; |
| private FileGroupsMetadata fileGroupsMetadata; |
| private final TestFlags flags = new TestFlags(); |
| @Mock EventLogger mockLogger; |
| @Mock SilentFeedback mockSilentFeedback; |
| |
| @Rule public final MockitoRule mocks = MockitoJUnit.rule(); |
| |
| @Before |
| public void setUp() throws Exception { |
| |
| context = ApplicationProvider.getApplicationContext(); |
| |
| testKey = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(false) |
| .build(); |
| |
| testKey2 = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP_2) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(false) |
| .build(); |
| |
| testKey3 = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP_3) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(false) |
| .build(); |
| |
| fileStorage = |
| new SynchronousFileStorage(Arrays.asList(AndroidFileBackend.builder(context).build())); |
| |
| testClock = new FakeTimeSource(); |
| SharedPreferencesFileGroupsMetadata sharedPreferencesImpl = |
| new SharedPreferencesFileGroupsMetadata( |
| context, testClock, mockSilentFeedback, instanceId, CONTROL_EXECUTOR); |
| switch (metadataStoreImpl) { |
| case SP_IMPL: |
| fileGroupsMetadata = sharedPreferencesImpl; |
| break; |
| } |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| fileGroupsMetadata.clear().get(); |
| } |
| |
| @Test |
| public void serializeAndDeserializeFileGroupKey() throws Exception { |
| String serializedGroupKey = FileGroupsMetadataUtil.getSerializedGroupKey(testKey, context); |
| GroupKey deserializedGroupKey = FileGroupsMetadataUtil.deserializeGroupKey(serializedGroupKey); |
| |
| assertThat(deserializedGroupKey.getGroupName()).isEqualTo(TEST_GROUP); |
| assertThat(deserializedGroupKey.getOwnerPackage()).isEqualTo(context.getPackageName()); |
| assertThat(deserializedGroupKey.getDownloaded()).isFalse(); |
| } |
| |
| @Test |
| public void readAndWriteFileGroup() throws Exception { |
| DataFileGroupInternal writeFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); |
| DataFileGroupInternal writeFileGroup2 = |
| MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1); |
| DataFileGroupInternal writeFileGroup3 = |
| MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 5); |
| |
| assertThat(fileGroupsMetadata.read(testKey).get()).isNull(); |
| assertThat(fileGroupsMetadata.write(testKey, writeFileGroup).get()).isTrue(); |
| |
| assertThat(fileGroupsMetadata.read(testKey2).get()).isNull(); |
| assertThat(fileGroupsMetadata.write(testKey2, writeFileGroup2).get()).isTrue(); |
| |
| assertThat(fileGroupsMetadata.read(testKey3).get()).isNull(); |
| assertThat(fileGroupsMetadata.write(testKey3, writeFileGroup3).get()).isTrue(); |
| |
| DataFileGroupInternal readFileGroup = fileGroupsMetadata.read(testKey).get(); |
| MddTestUtil.assertMessageEquals(readFileGroup, writeFileGroup); |
| |
| DataFileGroupInternal readFileGroup2 = fileGroupsMetadata.read(testKey2).get(); |
| MddTestUtil.assertMessageEquals(readFileGroup2, writeFileGroup2); |
| |
| DataFileGroupInternal readFileGroup3 = fileGroupsMetadata.read(testKey3).get(); |
| MddTestUtil.assertMessageEquals(readFileGroup3, writeFileGroup3); |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| @Test |
| public void readAndWriteFileGroup_withExtension() throws Exception { |
| DataFileGroupInternal writeFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); |
| |
| assertThat(fileGroupsMetadata.read(testKey).get()).isNull(); |
| assertThat(fileGroupsMetadata.write(testKey, writeFileGroup).get()).isTrue(); |
| |
| DataFileGroupInternal readFileGroup = fileGroupsMetadata.read(testKey).get(); |
| MddTestUtil.assertMessageEquals(readFileGroup, writeFileGroup); |
| |
| writeFileGroup = FileGroupUtil.setStaleExpirationDate(writeFileGroup, 1000); |
| assertThat(fileGroupsMetadata.write(testKey, writeFileGroup).get()).isTrue(); |
| |
| readFileGroup = fileGroupsMetadata.read(testKey).get(); |
| MddTestUtil.assertMessageEquals(readFileGroup, writeFileGroup); |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| @Test |
| public void removeFileGroup() throws Exception { |
| DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); |
| DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1); |
| DataFileGroupInternal fileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 5); |
| |
| assertThat(fileGroupsMetadata.write(testKey, fileGroup).get()).isTrue(); |
| assertThat(fileGroupsMetadata.remove(testKey).get()).isTrue(); |
| assertThat(fileGroupsMetadata.read(testKey).get()).isNull(); |
| |
| assertThat(fileGroupsMetadata.write(testKey2, fileGroup2).get()).isTrue(); |
| assertThat(fileGroupsMetadata.remove(testKey2).get()).isTrue(); |
| assertThat(fileGroupsMetadata.read(testKey2).get()).isNull(); |
| |
| assertThat(fileGroupsMetadata.write(testKey3, fileGroup3).get()).isTrue(); |
| assertThat(fileGroupsMetadata.remove(testKey3).get()).isTrue(); |
| assertThat(fileGroupsMetadata.read(testKey3).get()).isNull(); |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| @Test |
| public void readAndWriteFileGroupKeyProperties() throws Exception { |
| GroupKeyProperties writeGroupKeyProperties = |
| GroupKeyProperties.newBuilder().setActivatedOnDevice(true).build(); |
| GroupKeyProperties writeGroupKeyProperties2 = |
| GroupKeyProperties.newBuilder().setActivatedOnDevice(false).build(); |
| |
| assertThat(fileGroupsMetadata.readGroupKeyProperties(testKey).get()).isNull(); |
| assertThat(fileGroupsMetadata.writeGroupKeyProperties(testKey, writeGroupKeyProperties).get()) |
| .isTrue(); |
| |
| assertThat(fileGroupsMetadata.readGroupKeyProperties(testKey2).get()).isNull(); |
| assertThat(fileGroupsMetadata.writeGroupKeyProperties(testKey2, writeGroupKeyProperties2).get()) |
| .isTrue(); |
| |
| GroupKeyProperties readGroupKeyProperties = |
| fileGroupsMetadata.readGroupKeyProperties(testKey).get(); |
| MddTestUtil.assertMessageEquals(writeGroupKeyProperties, readGroupKeyProperties); |
| |
| GroupKeyProperties readGroupKeyProperties2 = |
| fileGroupsMetadata.readGroupKeyProperties(testKey2).get(); |
| MddTestUtil.assertMessageEquals(writeGroupKeyProperties2, readGroupKeyProperties2); |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| @Test |
| public void clear_removesAllMetadata() throws Exception { |
| DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); |
| DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1); |
| DataFileGroupInternal fileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 5); |
| |
| File parentDir = |
| new File(context.getFilesDir(), DirectoryUtil.MDD_STORAGE_MODULE + "/" + "shared"); |
| assertThat(parentDir.mkdirs()).isTrue(); |
| File garbageFile = FileGroupsMetadataUtil.getGarbageCollectorFile(context, instanceId); |
| |
| DataFileGroupInternal staleFileGroup = |
| MddTestUtil.createDataFileGroupInternal("stale-group", 2).toBuilder() |
| .setStaleLifetimeSecs(Duration.ofDays(1).getSeconds()) |
| .build(); |
| |
| assertThat(fileGroupsMetadata.write(testKey, fileGroup).get()).isTrue(); |
| assertThat(fileGroupsMetadata.write(testKey2, fileGroup2).get()).isTrue(); |
| assertThat(fileGroupsMetadata.write(testKey3, fileGroup3).get()).isTrue(); |
| assertThat(fileGroupsMetadata.addStaleGroup(staleFileGroup).get()).isTrue(); |
| |
| fileGroupsMetadata.clear().get(); |
| |
| assertThat(fileGroupsMetadata.read(testKey).get()).isNull(); |
| assertThat(fileGroupsMetadata.read(testKey2).get()).isNull(); |
| assertThat(fileGroupsMetadata.read(testKey3).get()).isNull(); |
| assertThat(garbageFile.exists()).isFalse(); |
| |
| for (File file : parentDir.listFiles()) { |
| boolean unused = file.delete(); |
| } |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| @Test |
| public void retrieveAllGroups() throws Exception { |
| GroupKey notSetDownloadedGroupKey = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP) |
| .setOwnerPackage(context.getPackageName()) |
| .build(); |
| |
| DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); |
| assertThat(fileGroupsMetadata.write(notSetDownloadedGroupKey, fileGroup1).get()).isTrue(); |
| |
| GroupKey setTrueDownloadedGroupKey = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP_2) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(true) |
| .build(); |
| DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2); |
| assertThat(fileGroupsMetadata.write(setTrueDownloadedGroupKey, fileGroup2).get()).isTrue(); |
| |
| GroupKey setFalseDownloadedGroupKey = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP_3) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(false) |
| .build(); |
| DataFileGroupInternal fileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 2); |
| assertThat(fileGroupsMetadata.write(setFalseDownloadedGroupKey, fileGroup3).get()).isTrue(); |
| |
| if (metadataStoreImpl == MetadataStoreImpl.SP_IMPL) { |
| // Garbage entry that will create null GroupKey |
| SharedPreferences prefs = |
| SharedPreferencesUtil.getSharedPreferences( |
| context, FileGroupsMetadataUtil.MDD_FILE_GROUPS, instanceId); |
| prefs.edit().putString("garbage-key", "garbage-value").commit(); |
| } |
| |
| List<Pair<GroupKey, DataFileGroupInternal>> allGroups = |
| fileGroupsMetadata.getAllFreshGroups().get(); |
| assertThat(allGroups).hasSize(3); |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| @Test |
| public void removeGroups_noGroups() throws Exception { |
| // Newer pending version of this group. |
| DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); |
| GroupKey key1 = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(false) |
| .build(); |
| writePendingFileGroupToSharedPrefs(key1, fileGroup1); |
| |
| // Older downloaded version of the same group |
| DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); |
| GroupKey key2 = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(true) |
| .build(); |
| writeDownloadedFileGroupToSharedPrefs(key2, fileGroup2); |
| |
| assertThat(fileGroupsMetadata.removeAllGroupsWithKeys(ImmutableList.of()).get()).isTrue(); |
| |
| assertThat(readPendingFileGroupFromSharedPrefs(key1, true /*shouldExist*/)) |
| .isEqualTo(fileGroup1); |
| assertThat(readDownloadedFileGroupFromSharedPrefs(key2, true /*shouldExist*/)) |
| .isEqualTo(fileGroup2); |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| @Test |
| public void removeGroups_removePendingGroup() throws Exception { |
| // Newer pending version of this group. |
| DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); |
| GroupKey key1 = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(false) |
| .build(); |
| writePendingFileGroupToSharedPrefs(key1, fileGroup1); |
| |
| // Older downloaded version of the same group |
| DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); |
| GroupKey key2 = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(true) |
| .build(); |
| writeDownloadedFileGroupToSharedPrefs(key2, fileGroup2); |
| |
| assertThat(fileGroupsMetadata.removeAllGroupsWithKeys(Arrays.asList(key1)).get()).isTrue(); |
| |
| readPendingFileGroupFromSharedPrefs(key1, false /*shouldExist*/); |
| assertThat(readDownloadedFileGroupFromSharedPrefs(key2, true /*shouldExist*/)) |
| .isEqualTo(fileGroup2); |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| @Test |
| public void removeGroups_removeDownloadedGroup() throws Exception { |
| // Newer pending version of this group. |
| DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); |
| GroupKey key1 = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(false) |
| .build(); |
| writePendingFileGroupToSharedPrefs(key1, fileGroup1); |
| |
| // Older downloaded version of the same group |
| DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); |
| GroupKey key2 = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(true) |
| .build(); |
| writeDownloadedFileGroupToSharedPrefs(key2, fileGroup2); |
| |
| assertThat(fileGroupsMetadata.removeAllGroupsWithKeys(Arrays.asList(key2)).get()).isTrue(); |
| |
| assertThat(readPendingFileGroupFromSharedPrefs(key1, true /*shouldExist*/)) |
| .isEqualTo(fileGroup1); |
| readDownloadedFileGroupFromSharedPrefs(key2, false /*shouldExist*/); |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| @Test |
| public void addStaleGroup_multipleGroups() throws Exception { |
| long staleExpirationLifetimeSecs = 1000; |
| |
| DataFileGroupInternal fileGroup1 = |
| MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder() |
| .setStaleLifetimeSecs(staleExpirationLifetimeSecs) |
| .build(); |
| DataFileGroupInternal fileGroup2 = |
| MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 3).toBuilder() |
| .setStaleLifetimeSecs(staleExpirationLifetimeSecs) |
| .build(); |
| |
| assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty(); |
| |
| testClock.set(15000 /* 15 seconds */); |
| assertThat(fileGroupsMetadata.addStaleGroup(fileGroup1).get()).isTrue(); |
| assertThat(fileGroupsMetadata.addStaleGroup(fileGroup2).get()).isTrue(); |
| |
| List<DataFileGroupInternal> staleGroups = fileGroupsMetadata.getAllStaleGroups().get(); |
| assertThat(staleGroups).hasSize(2); |
| |
| fileGroup1 = FileGroupUtil.setStaleExpirationDate(fileGroup1, staleExpirationLifetimeSecs + 15); |
| fileGroup2 = FileGroupUtil.setStaleExpirationDate(fileGroup2, staleExpirationLifetimeSecs + 15); |
| |
| assertThat(staleGroups.get(0)).isEqualTo(fileGroup1); |
| assertThat(staleGroups.get(1)).isEqualTo(fileGroup2); |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| @Test |
| public void removeAllStaleGroups_multipleGroups() throws Exception { |
| long staleExpirationLifetimeSecs = 1000; |
| |
| List<DataFileGroupInternal> fileGroups = new ArrayList<>(); |
| DataFileGroupInternal fileGroup1 = |
| MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder() |
| .setStaleLifetimeSecs(staleExpirationLifetimeSecs) |
| .build(); |
| fileGroups.add(fileGroup1); |
| |
| DataFileGroupInternal fileGroup2 = |
| MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 3).toBuilder() |
| .setStaleLifetimeSecs(staleExpirationLifetimeSecs) |
| .build(); |
| fileGroups.add(fileGroup2); |
| |
| assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty(); |
| |
| assertThat(fileGroupsMetadata.writeStaleGroups(fileGroups).get()).isTrue(); |
| assertThat(fileGroupsMetadata.getAllStaleGroups().get()).hasSize(2); |
| |
| fileGroupsMetadata.removeAllStaleGroups().get(); |
| assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty(); |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| @Test |
| public void writeStaleGroups_noGroup() throws Exception { |
| List<DataFileGroupInternal> fileGroups = new ArrayList<>(); |
| assertThat(fileGroupsMetadata.writeStaleGroups(fileGroups).get()).isTrue(); |
| assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty(); |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| /** |
| * This test mainly exists to ensure that the garbage collector handles IO operations correctly |
| * for large inputs. |
| */ |
| @Test |
| public void writeAndReadStaleGroups_onLotsOfFileGroups() throws Exception { |
| long staleExpirationDate = 1000; |
| |
| // Create files on device so that the garbage collector can delete them |
| List<DataFileGroupInternal> fileGroups = new ArrayList<>(); |
| for (int i = 0; i < 5; ++i) { |
| DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal("group" + i, 1); |
| dataFileGroup = FileGroupUtil.setStaleExpirationDate(dataFileGroup, staleExpirationDate); |
| fileGroups.add(dataFileGroup); |
| } |
| |
| assertThat(fileGroupsMetadata.writeStaleGroups(fileGroups).get()).isTrue(); |
| assertThat( |
| fileGroupsMetadata |
| .getAllStaleGroups() |
| .get() |
| .get(0) |
| .getBookkeeping() |
| .getStaleExpirationDate()) |
| .isEqualTo(1000); |
| assertThat(fileGroupsMetadata.getAllStaleGroups().get()).containsExactlyElementsIn(fileGroups); |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| /** |
| * This test mainly exists to ensure that after migrating the group metadata storage proto from |
| * {@link DataFileGroup} to {@link DataFileGroupInternal}, MDD is still able to parse the group |
| * metadata which was previously written to disk before the migration. |
| */ |
| @Test |
| public void writeAndReadGroups_migration_fromDataFileGroup_toDataFileGroupInternal() |
| throws Exception { |
| DataFileGroup fileGroup1 = MddTestUtil.createDataFileGroup(TEST_GROUP, 2); |
| GroupKey key1 = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(false) |
| .build(); |
| assertThat(writeDataFileGroup(key1, fileGroup1, instanceId)).isTrue(); |
| |
| // Older downloaded version of the same group |
| DataFileGroup fileGroup2 = MddTestUtil.createDataFileGroup(TEST_GROUP, 1); |
| GroupKey key2 = |
| GroupKey.newBuilder() |
| .setGroupName(TEST_GROUP) |
| .setOwnerPackage(context.getPackageName()) |
| .setDownloaded(true) |
| .build(); |
| assertThat(writeDataFileGroup(key2, fileGroup2, instanceId)).isTrue(); |
| |
| // Make sure that parsing DataFileGroup to DataFileGroupInternal produces identical result as |
| // calling proto convert. |
| assertThat(fileGroupsMetadata.read(key1).get()) |
| .isEqualTo(ProtoConversionUtil.convert(fileGroup1)); |
| assertThat(fileGroupsMetadata.read(key2).get()) |
| .isEqualTo(ProtoConversionUtil.convert(fileGroup2)); |
| |
| verifyNoErrorInPdsMigration(); |
| } |
| |
| @Test |
| public void garbageCollectorFileSeparation() throws Exception { |
| SharedPreferencesFileGroupsMetadata fileGroupsMetadataAbsent = |
| new SharedPreferencesFileGroupsMetadata( |
| context, testClock, mockSilentFeedback, Optional.absent(), CONTROL_EXECUTOR); |
| |
| SharedPreferencesFileGroupsMetadata fileGroupsMetadata2 = |
| new SharedPreferencesFileGroupsMetadata( |
| context, testClock, mockSilentFeedback, Optional.of("instance2"), CONTROL_EXECUTOR); |
| |
| SharedPreferencesFileGroupsMetadata fileGroupsMetadata3 = |
| new SharedPreferencesFileGroupsMetadata( |
| context, testClock, mockSilentFeedback, Optional.of("instance3"), CONTROL_EXECUTOR); |
| |
| assertThat(fileGroupsMetadataAbsent.getGarbageCollectorFile().getAbsolutePath()) |
| .isNotEqualTo(fileGroupsMetadata2.getGarbageCollectorFile().getAbsolutePath()); |
| |
| assertThat(fileGroupsMetadata2.getGarbageCollectorFile().getAbsolutePath()) |
| .isNotEqualTo(fileGroupsMetadata3.getGarbageCollectorFile().getAbsolutePath()); |
| } |
| |
| /** |
| * Writes {@link DataFileGroup} into disk. The main purpose of this method is for the convenience |
| * of migration tests. Previously, the file group metadata is stored in DataFileGroup with |
| * extensions. We wanted to make sure that after migrating to {@link DataFileGroupInternal}, the |
| * previous metadata can still be parsed. |
| */ |
| boolean writeDataFileGroup( |
| GroupKey groupKey, DataFileGroup fileGroup, Optional<String> instanceId) { |
| String serializedGroupKey = FileGroupsMetadataUtil.getSerializedGroupKey(groupKey, context); |
| SharedPreferences prefs = |
| SharedPreferencesUtil.getSharedPreferences( |
| context, FileGroupsMetadataUtil.MDD_FILE_GROUPS, instanceId); |
| return SharedPreferencesUtil.writeProto(prefs, serializedGroupKey, fileGroup); |
| } |
| |
| private DataFileGroupInternal readPendingFileGroupFromSharedPrefs( |
| GroupKey key, boolean shouldExist) throws Exception { |
| GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(false).build(); |
| return readFileGroupFromSharedPrefs(duplicateGroupKey, shouldExist); |
| } |
| |
| private void writePendingFileGroupToSharedPrefs(GroupKey key, DataFileGroupInternal group) |
| throws Exception { |
| GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(false).build(); |
| assertThat(fileGroupsMetadata.write(duplicateGroupKey, group).get()).isTrue(); |
| } |
| |
| private DataFileGroupInternal readDownloadedFileGroupFromSharedPrefs( |
| GroupKey key, boolean shouldExist) throws Exception { |
| GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(true).build(); |
| return readFileGroupFromSharedPrefs(duplicateGroupKey, shouldExist); |
| } |
| |
| private void writeDownloadedFileGroupToSharedPrefs(GroupKey key, DataFileGroupInternal group) |
| throws Exception { |
| GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(true).build(); |
| assertThat(fileGroupsMetadata.write(duplicateGroupKey, group).get()).isTrue(); |
| } |
| |
| private DataFileGroupInternal readFileGroupFromSharedPrefs(GroupKey key, boolean shouldExist) |
| throws Exception { |
| DataFileGroupInternal group = fileGroupsMetadata.read(key).get(); |
| if (shouldExist) { |
| assertWithMessage(String.format("Expected that key %s should exist.", key)) |
| .that(group) |
| .isNotNull(); |
| } else { |
| assertWithMessage(String.format("Expected that key %s should not exist.", key)) |
| .that(group) |
| .isNull(); |
| } |
| return group; |
| } |
| |
| private void verifyNoErrorInPdsMigration() {} |
| } |