• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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 package com.google.android.libraries.mobiledatadownload.internal;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import android.content.Context;
22 import android.content.SharedPreferences;
23 import android.net.Uri;
24 import androidx.test.core.app.ApplicationProvider;
25 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal;
26 import com.google.mobiledatadownload.internal.MetadataProto.GroupKey;
27 import com.google.mobiledatadownload.internal.MetadataProto.GroupKeyProperties;
28 import com.google.android.libraries.mobiledatadownload.SilentFeedback;
29 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
30 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend;
31 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUri;
32 import com.google.android.libraries.mobiledatadownload.internal.collect.GroupKeyAndGroup;
33 import com.google.android.libraries.mobiledatadownload.internal.logging.EventLogger;
34 import com.google.android.libraries.mobiledatadownload.internal.util.DirectoryUtil;
35 import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupUtil;
36 import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupsMetadataUtil;
37 import com.google.android.libraries.mobiledatadownload.internal.util.ProtoConversionUtil;
38 import com.google.android.libraries.mobiledatadownload.internal.util.SharedPreferencesUtil;
39 import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource;
40 import com.google.android.libraries.mobiledatadownload.testing.TestFlags;
41 import com.google.common.base.Optional;
42 import com.google.common.collect.ImmutableList;
43 import com.google.common.util.concurrent.MoreExecutors;
44 import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup;
45 import java.io.File;
46 import java.time.Duration;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Collection;
50 import java.util.List;
51 import java.util.concurrent.Executor;
52 import java.util.concurrent.Executors;
53 import org.junit.After;
54 import org.junit.Before;
55 import org.junit.Rule;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 import org.mockito.Mock;
59 import org.mockito.junit.MockitoJUnit;
60 import org.mockito.junit.MockitoRule;
61 import org.robolectric.ParameterizedRobolectricTestRunner;
62 import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
63 import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
64 
65 @RunWith(ParameterizedRobolectricTestRunner.class)
66 public class FileGroupsMetadataTest {
67 
68   // TODO(b/26110951): use Parameterized runner once android_test supports it
69   private enum MetadataStoreImpl {
70     SP_IMPL,
71   }
72 
73   // Whether to use PDS metadata store or SharedPreferences metadata store.
74   @Parameter(value = 0)
75   public MetadataStoreImpl metadataStoreImpl;
76 
77   @Parameter(value = 1)
78   public Optional<String> instanceId;
79 
80   @Parameters(name = "metadataStoreImpl = {0} instanceId = {1}")
parameters()81   public static Collection<Object[]> parameters() {
82     return Arrays.asList(
83         new Object[][] {
84           {MetadataStoreImpl.SP_IMPL, Optional.absent()},
85           {MetadataStoreImpl.SP_IMPL, Optional.of("id")},
86         });
87   }
88 
89   private static final String TEST_GROUP = "test-group";
90   private static final String TEST_GROUP_2 = "test-group-2";
91   private static final String TEST_GROUP_3 = "test-group-3";
92   private static final Executor CONTROL_EXECUTOR =
93       MoreExecutors.newSequentialExecutor(Executors.newCachedThreadPool());
94 
95   private static GroupKey testKey;
96   private static GroupKey testKey2;
97   private static GroupKey testKey3;
98 
99   private SynchronousFileStorage fileStorage;
100   private Context context;
101   private FakeTimeSource testClock;
102   private FileGroupsMetadata fileGroupsMetadata;
103   private Uri destinationUri;
104   private Uri diagnosticUri;
105   private final TestFlags flags = new TestFlags();
106 
107   @Mock EventLogger mockLogger;
108   @Mock SilentFeedback mockSilentFeedback;
109 
110   @Rule public final MockitoRule mocks = MockitoJUnit.rule();
111 
112   @Before
setUp()113   public void setUp() throws Exception {
114 
115     context = ApplicationProvider.getApplicationContext();
116 
117     testKey =
118         GroupKey.newBuilder()
119             .setGroupName(TEST_GROUP)
120             .setOwnerPackage(context.getPackageName())
121             .setDownloaded(false)
122             .build();
123 
124     testKey2 =
125         GroupKey.newBuilder()
126             .setGroupName(TEST_GROUP_2)
127             .setOwnerPackage(context.getPackageName())
128             .setDownloaded(false)
129             .build();
130 
131     testKey3 =
132         GroupKey.newBuilder()
133             .setGroupName(TEST_GROUP_3)
134             .setOwnerPackage(context.getPackageName())
135             .setDownloaded(false)
136             .build();
137 
138     fileStorage =
139         new SynchronousFileStorage(Arrays.asList(AndroidFileBackend.builder(context).build()));
140 
141     testClock = new FakeTimeSource();
142     destinationUri =
143         AndroidUri.builder(context)
144             .setPackage(context.getPackageName())
145             .setRelativePath("dest.pb")
146             .build();
147     diagnosticUri =
148         AndroidUri.builder(context)
149             .setPackage(context.getPackageName())
150             .setRelativePath("diag.pb")
151             .build();
152     SharedPreferencesFileGroupsMetadata sharedPreferencesImpl =
153         new SharedPreferencesFileGroupsMetadata(
154             context, testClock, mockSilentFeedback, instanceId, CONTROL_EXECUTOR);
155     switch (metadataStoreImpl) {
156       case SP_IMPL:
157         fileGroupsMetadata = sharedPreferencesImpl;
158         break;
159     }
160   }
161 
162   @After
tearDown()163   public void tearDown() throws Exception {
164     if (fileStorage.exists(diagnosticUri)) {
165       fileStorage.deleteFile(diagnosticUri);
166     }
167     if (fileStorage.exists(destinationUri)) {
168       fileStorage.deleteFile(destinationUri);
169     }
170     fileGroupsMetadata.clear().get();
171   }
172 
173   @Test
serializeAndDeserializeFileGroupKey()174   public void serializeAndDeserializeFileGroupKey() throws Exception {
175     String serializedGroupKey = FileGroupsMetadataUtil.getSerializedGroupKey(testKey);
176     GroupKey deserializedGroupKey = FileGroupsMetadataUtil.deserializeGroupKey(serializedGroupKey);
177 
178     assertThat(deserializedGroupKey.getGroupName()).isEqualTo(TEST_GROUP);
179     assertThat(deserializedGroupKey.getOwnerPackage()).isEqualTo(context.getPackageName());
180     assertThat(deserializedGroupKey.getDownloaded()).isFalse();
181   }
182 
183   @Test
readAndWriteFileGroup()184   public void readAndWriteFileGroup() throws Exception {
185     DataFileGroupInternal writeFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
186     DataFileGroupInternal writeFileGroup2 =
187         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1);
188     DataFileGroupInternal writeFileGroup3 =
189         MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 5);
190 
191     assertThat(fileGroupsMetadata.read(testKey).get()).isNull();
192     assertThat(fileGroupsMetadata.write(testKey, writeFileGroup).get()).isTrue();
193 
194     assertThat(fileGroupsMetadata.read(testKey2).get()).isNull();
195     assertThat(fileGroupsMetadata.write(testKey2, writeFileGroup2).get()).isTrue();
196 
197     assertThat(fileGroupsMetadata.read(testKey3).get()).isNull();
198     assertThat(fileGroupsMetadata.write(testKey3, writeFileGroup3).get()).isTrue();
199 
200     DataFileGroupInternal readFileGroup = fileGroupsMetadata.read(testKey).get();
201     MddTestUtil.assertMessageEquals(readFileGroup, writeFileGroup);
202 
203     DataFileGroupInternal readFileGroup2 = fileGroupsMetadata.read(testKey2).get();
204     MddTestUtil.assertMessageEquals(readFileGroup2, writeFileGroup2);
205 
206     DataFileGroupInternal readFileGroup3 = fileGroupsMetadata.read(testKey3).get();
207     MddTestUtil.assertMessageEquals(readFileGroup3, writeFileGroup3);
208 
209     verifyNoErrorInPdsMigration();
210   }
211 
212   @Test
readAndWriteFileGroup_withExtension()213   public void readAndWriteFileGroup_withExtension() throws Exception {
214     DataFileGroupInternal writeFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
215 
216     assertThat(fileGroupsMetadata.read(testKey).get()).isNull();
217     assertThat(fileGroupsMetadata.write(testKey, writeFileGroup).get()).isTrue();
218 
219     DataFileGroupInternal readFileGroup = fileGroupsMetadata.read(testKey).get();
220     MddTestUtil.assertMessageEquals(readFileGroup, writeFileGroup);
221 
222     writeFileGroup = FileGroupUtil.setStaleExpirationDate(writeFileGroup, 1000);
223     assertThat(fileGroupsMetadata.write(testKey, writeFileGroup).get()).isTrue();
224 
225     readFileGroup = fileGroupsMetadata.read(testKey).get();
226     MddTestUtil.assertMessageEquals(readFileGroup, writeFileGroup);
227 
228     verifyNoErrorInPdsMigration();
229   }
230 
231   @Test
removeFileGroup()232   public void removeFileGroup() throws Exception {
233     DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
234     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1);
235     DataFileGroupInternal fileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 5);
236 
237     assertThat(fileGroupsMetadata.write(testKey, fileGroup).get()).isTrue();
238     assertThat(fileGroupsMetadata.remove(testKey).get()).isTrue();
239     assertThat(fileGroupsMetadata.read(testKey).get()).isNull();
240 
241     assertThat(fileGroupsMetadata.write(testKey2, fileGroup2).get()).isTrue();
242     assertThat(fileGroupsMetadata.remove(testKey2).get()).isTrue();
243     assertThat(fileGroupsMetadata.read(testKey2).get()).isNull();
244 
245     assertThat(fileGroupsMetadata.write(testKey3, fileGroup3).get()).isTrue();
246     assertThat(fileGroupsMetadata.remove(testKey3).get()).isTrue();
247     assertThat(fileGroupsMetadata.read(testKey3).get()).isNull();
248 
249     verifyNoErrorInPdsMigration();
250   }
251 
252   @Test
readAndWriteFileGroupKeyProperties()253   public void readAndWriteFileGroupKeyProperties() throws Exception {
254     GroupKeyProperties writeGroupKeyProperties =
255         GroupKeyProperties.newBuilder().setActivatedOnDevice(true).build();
256     GroupKeyProperties writeGroupKeyProperties2 =
257         GroupKeyProperties.newBuilder().setActivatedOnDevice(false).build();
258 
259     assertThat(fileGroupsMetadata.readGroupKeyProperties(testKey).get()).isNull();
260     assertThat(fileGroupsMetadata.writeGroupKeyProperties(testKey, writeGroupKeyProperties).get())
261         .isTrue();
262 
263     assertThat(fileGroupsMetadata.readGroupKeyProperties(testKey2).get()).isNull();
264     assertThat(fileGroupsMetadata.writeGroupKeyProperties(testKey2, writeGroupKeyProperties2).get())
265         .isTrue();
266 
267     GroupKeyProperties readGroupKeyProperties =
268         fileGroupsMetadata.readGroupKeyProperties(testKey).get();
269     MddTestUtil.assertMessageEquals(writeGroupKeyProperties, readGroupKeyProperties);
270 
271     GroupKeyProperties readGroupKeyProperties2 =
272         fileGroupsMetadata.readGroupKeyProperties(testKey2).get();
273     MddTestUtil.assertMessageEquals(writeGroupKeyProperties2, readGroupKeyProperties2);
274 
275     verifyNoErrorInPdsMigration();
276   }
277 
278   @Test
clear_removesAllMetadata()279   public void clear_removesAllMetadata() throws Exception {
280     DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
281     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1);
282     DataFileGroupInternal fileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 5);
283 
284     File parentDir =
285         new File(context.getFilesDir(), DirectoryUtil.MDD_STORAGE_MODULE + "/" + "shared");
286     assertThat(parentDir.mkdirs()).isTrue();
287     File garbageFile = FileGroupsMetadataUtil.getGarbageCollectorFile(context, instanceId);
288 
289     DataFileGroupInternal staleFileGroup =
290         MddTestUtil.createDataFileGroupInternal("stale-group", 2).toBuilder()
291             .setStaleLifetimeSecs(Duration.ofDays(1).getSeconds())
292             .build();
293 
294     assertThat(fileGroupsMetadata.write(testKey, fileGroup).get()).isTrue();
295     assertThat(fileGroupsMetadata.write(testKey2, fileGroup2).get()).isTrue();
296     assertThat(fileGroupsMetadata.write(testKey3, fileGroup3).get()).isTrue();
297     assertThat(fileGroupsMetadata.addStaleGroup(staleFileGroup).get()).isTrue();
298 
299     fileGroupsMetadata.clear().get();
300 
301     assertThat(fileGroupsMetadata.read(testKey).get()).isNull();
302     assertThat(fileGroupsMetadata.read(testKey2).get()).isNull();
303     assertThat(fileGroupsMetadata.read(testKey3).get()).isNull();
304     assertThat(garbageFile.exists()).isFalse();
305 
306     for (File file : parentDir.listFiles()) {
307       boolean unused = file.delete();
308     }
309 
310     verifyNoErrorInPdsMigration();
311   }
312 
313   @Test
retrieveAllGroups()314   public void retrieveAllGroups() throws Exception {
315     GroupKey notSetDownloadedGroupKey =
316         GroupKey.newBuilder()
317             .setGroupName(TEST_GROUP)
318             .setOwnerPackage(context.getPackageName())
319             .build();
320 
321     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
322     assertThat(fileGroupsMetadata.write(notSetDownloadedGroupKey, fileGroup1).get()).isTrue();
323 
324     GroupKey setTrueDownloadedGroupKey =
325         GroupKey.newBuilder()
326             .setGroupName(TEST_GROUP_2)
327             .setOwnerPackage(context.getPackageName())
328             .setDownloaded(true)
329             .build();
330     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2);
331     assertThat(fileGroupsMetadata.write(setTrueDownloadedGroupKey, fileGroup2).get()).isTrue();
332 
333     GroupKey setFalseDownloadedGroupKey =
334         GroupKey.newBuilder()
335             .setGroupName(TEST_GROUP_3)
336             .setOwnerPackage(context.getPackageName())
337             .setDownloaded(false)
338             .build();
339     DataFileGroupInternal fileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 2);
340     assertThat(fileGroupsMetadata.write(setFalseDownloadedGroupKey, fileGroup3).get()).isTrue();
341 
342     if (metadataStoreImpl == MetadataStoreImpl.SP_IMPL) {
343       // Garbage entry that will create null GroupKey
344       SharedPreferences prefs =
345           SharedPreferencesUtil.getSharedPreferences(
346               context, FileGroupsMetadataUtil.MDD_FILE_GROUPS, instanceId);
347       prefs.edit().putString("garbage-key", "garbage-value").commit();
348     }
349 
350     List<GroupKeyAndGroup> allGroups = fileGroupsMetadata.getAllFreshGroups().get();
351     assertThat(allGroups).hasSize(3);
352 
353     verifyNoErrorInPdsMigration();
354   }
355 
356   @Test
removeGroups_noGroups()357   public void removeGroups_noGroups() throws Exception {
358     // Newer pending version of this group.
359     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
360     GroupKey key1 =
361         GroupKey.newBuilder()
362             .setGroupName(TEST_GROUP)
363             .setOwnerPackage(context.getPackageName())
364             .setDownloaded(false)
365             .build();
366     writePendingFileGroupToSharedPrefs(key1, fileGroup1);
367 
368     // Older downloaded version of the same group
369     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
370     GroupKey key2 =
371         GroupKey.newBuilder()
372             .setGroupName(TEST_GROUP)
373             .setOwnerPackage(context.getPackageName())
374             .setDownloaded(true)
375             .build();
376     writeDownloadedFileGroupToSharedPrefs(key2, fileGroup2);
377 
378     assertThat(fileGroupsMetadata.removeAllGroupsWithKeys(ImmutableList.of()).get()).isTrue();
379 
380     assertThat(readPendingFileGroupFromSharedPrefs(key1, true /*shouldExist*/))
381         .isEqualTo(fileGroup1);
382     assertThat(readDownloadedFileGroupFromSharedPrefs(key2, true /*shouldExist*/))
383         .isEqualTo(fileGroup2);
384 
385     verifyNoErrorInPdsMigration();
386   }
387 
388   @Test
removeGroups_removePendingGroup()389   public void removeGroups_removePendingGroup() throws Exception {
390     // Newer pending version of this group.
391     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
392     GroupKey key1 =
393         GroupKey.newBuilder()
394             .setGroupName(TEST_GROUP)
395             .setOwnerPackage(context.getPackageName())
396             .setDownloaded(false)
397             .build();
398     writePendingFileGroupToSharedPrefs(key1, fileGroup1);
399 
400     // Older downloaded version of the same group
401     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
402     GroupKey key2 =
403         GroupKey.newBuilder()
404             .setGroupName(TEST_GROUP)
405             .setOwnerPackage(context.getPackageName())
406             .setDownloaded(true)
407             .build();
408     writeDownloadedFileGroupToSharedPrefs(key2, fileGroup2);
409 
410     assertThat(fileGroupsMetadata.removeAllGroupsWithKeys(Arrays.asList(key1)).get()).isTrue();
411 
412     readPendingFileGroupFromSharedPrefs(key1, false /*shouldExist*/);
413     assertThat(readDownloadedFileGroupFromSharedPrefs(key2, true /*shouldExist*/))
414         .isEqualTo(fileGroup2);
415 
416     verifyNoErrorInPdsMigration();
417   }
418 
419   @Test
removeGroups_removeDownloadedGroup()420   public void removeGroups_removeDownloadedGroup() throws Exception {
421     // Newer pending version of this group.
422     DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
423     GroupKey key1 =
424         GroupKey.newBuilder()
425             .setGroupName(TEST_GROUP)
426             .setOwnerPackage(context.getPackageName())
427             .setDownloaded(false)
428             .build();
429     writePendingFileGroupToSharedPrefs(key1, fileGroup1);
430 
431     // Older downloaded version of the same group
432     DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
433     GroupKey key2 =
434         GroupKey.newBuilder()
435             .setGroupName(TEST_GROUP)
436             .setOwnerPackage(context.getPackageName())
437             .setDownloaded(true)
438             .build();
439     writeDownloadedFileGroupToSharedPrefs(key2, fileGroup2);
440 
441     assertThat(fileGroupsMetadata.removeAllGroupsWithKeys(Arrays.asList(key2)).get()).isTrue();
442 
443     assertThat(readPendingFileGroupFromSharedPrefs(key1, true /*shouldExist*/))
444         .isEqualTo(fileGroup1);
445     readDownloadedFileGroupFromSharedPrefs(key2, false /*shouldExist*/);
446 
447     verifyNoErrorInPdsMigration();
448   }
449 
450   @Test
addStaleGroup_multipleGroups()451   public void addStaleGroup_multipleGroups() throws Exception {
452     long staleExpirationLifetimeSecs = 1000;
453 
454     DataFileGroupInternal fileGroup1 =
455         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
456             .setStaleLifetimeSecs(staleExpirationLifetimeSecs)
457             .build();
458     DataFileGroupInternal fileGroup2 =
459         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 3).toBuilder()
460             .setStaleLifetimeSecs(staleExpirationLifetimeSecs)
461             .build();
462 
463     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty();
464 
465     testClock.set(15000 /* 15 seconds */);
466     assertThat(fileGroupsMetadata.addStaleGroup(fileGroup1).get()).isTrue();
467     assertThat(fileGroupsMetadata.addStaleGroup(fileGroup2).get()).isTrue();
468 
469     List<DataFileGroupInternal> staleGroups = fileGroupsMetadata.getAllStaleGroups().get();
470     assertThat(staleGroups).hasSize(2);
471 
472     fileGroup1 = FileGroupUtil.setStaleExpirationDate(fileGroup1, staleExpirationLifetimeSecs + 15);
473     fileGroup2 = FileGroupUtil.setStaleExpirationDate(fileGroup2, staleExpirationLifetimeSecs + 15);
474 
475     assertThat(staleGroups.get(0)).isEqualTo(fileGroup1);
476     assertThat(staleGroups.get(1)).isEqualTo(fileGroup2);
477 
478     verifyNoErrorInPdsMigration();
479   }
480 
481   @Test
removeAllStaleGroups_multipleGroups()482   public void removeAllStaleGroups_multipleGroups() throws Exception {
483     long staleExpirationLifetimeSecs = 1000;
484 
485     List<DataFileGroupInternal> fileGroups = new ArrayList<>();
486     DataFileGroupInternal fileGroup1 =
487         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
488             .setStaleLifetimeSecs(staleExpirationLifetimeSecs)
489             .build();
490     fileGroups.add(fileGroup1);
491 
492     DataFileGroupInternal fileGroup2 =
493         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 3).toBuilder()
494             .setStaleLifetimeSecs(staleExpirationLifetimeSecs)
495             .build();
496     fileGroups.add(fileGroup2);
497 
498     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty();
499 
500     assertThat(fileGroupsMetadata.writeStaleGroups(fileGroups).get()).isTrue();
501     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).hasSize(2);
502 
503     fileGroupsMetadata.removeAllStaleGroups().get();
504     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty();
505 
506     verifyNoErrorInPdsMigration();
507   }
508 
509   @Test
writeStaleGroups_noGroup()510   public void writeStaleGroups_noGroup() throws Exception {
511     List<DataFileGroupInternal> fileGroups = new ArrayList<>();
512     assertThat(fileGroupsMetadata.writeStaleGroups(fileGroups).get()).isTrue();
513     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty();
514     verifyNoErrorInPdsMigration();
515   }
516 
517   /**
518    * This test mainly exists to ensure that the garbage collector handles IO operations correctly
519    * for large inputs.
520    */
521   @Test
writeAndReadStaleGroups_onLotsOfFileGroups()522   public void writeAndReadStaleGroups_onLotsOfFileGroups() throws Exception {
523     long staleExpirationDate = 1000;
524 
525     // Create files on device so that the garbage collector can delete them
526     List<DataFileGroupInternal> fileGroups = new ArrayList<>();
527     for (int i = 0; i < 5; ++i) {
528       DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal("group" + i, 1);
529       dataFileGroup = FileGroupUtil.setStaleExpirationDate(dataFileGroup, staleExpirationDate);
530       fileGroups.add(dataFileGroup);
531     }
532 
533     assertThat(fileGroupsMetadata.writeStaleGroups(fileGroups).get()).isTrue();
534     assertThat(
535             fileGroupsMetadata
536                 .getAllStaleGroups()
537                 .get()
538                 .get(0)
539                 .getBookkeeping()
540                 .getStaleExpirationDate())
541         .isEqualTo(1000);
542     assertThat(fileGroupsMetadata.getAllStaleGroups().get()).containsExactlyElementsIn(fileGroups);
543 
544     verifyNoErrorInPdsMigration();
545   }
546 
547   /**
548    * This test mainly exists to ensure that after migrating the group metadata storage proto from
549    * {@link DataFileGroup} to {@link DataFileGroupInternal}, MDD is still able to parse the group
550    * metadata which was previously written to disk before the migration.
551    */
552   @Test
writeAndReadGroups_migration_fromDataFileGroup_toDataFileGroupInternal()553   public void writeAndReadGroups_migration_fromDataFileGroup_toDataFileGroupInternal()
554       throws Exception {
555     DataFileGroup fileGroup1 = MddTestUtil.createDataFileGroup(TEST_GROUP, 2);
556     GroupKey key1 =
557         GroupKey.newBuilder()
558             .setGroupName(TEST_GROUP)
559             .setOwnerPackage(context.getPackageName())
560             .setDownloaded(false)
561             .build();
562     assertThat(writeDataFileGroup(key1, fileGroup1, instanceId)).isTrue();
563 
564     // Older downloaded version of the same group
565     DataFileGroup fileGroup2 = MddTestUtil.createDataFileGroup(TEST_GROUP, 1);
566     GroupKey key2 =
567         GroupKey.newBuilder()
568             .setGroupName(TEST_GROUP)
569             .setOwnerPackage(context.getPackageName())
570             .setDownloaded(true)
571             .build();
572     assertThat(writeDataFileGroup(key2, fileGroup2, instanceId)).isTrue();
573 
574     // Make sure that parsing DataFileGroup to DataFileGroupInternal produces identical result as
575     // calling proto convert.
576     assertThat(fileGroupsMetadata.read(key1).get())
577         .isEqualTo(ProtoConversionUtil.convert(fileGroup1));
578     assertThat(fileGroupsMetadata.read(key2).get())
579         .isEqualTo(ProtoConversionUtil.convert(fileGroup2));
580 
581     verifyNoErrorInPdsMigration();
582   }
583 
584   @Test
garbageCollectorFileSeparation()585   public void garbageCollectorFileSeparation() throws Exception {
586     SharedPreferencesFileGroupsMetadata fileGroupsMetadataAbsent =
587         new SharedPreferencesFileGroupsMetadata(
588             context, testClock, mockSilentFeedback, Optional.absent(), CONTROL_EXECUTOR);
589 
590     SharedPreferencesFileGroupsMetadata fileGroupsMetadata2 =
591         new SharedPreferencesFileGroupsMetadata(
592             context, testClock, mockSilentFeedback, Optional.of("instance2"), CONTROL_EXECUTOR);
593 
594     SharedPreferencesFileGroupsMetadata fileGroupsMetadata3 =
595         new SharedPreferencesFileGroupsMetadata(
596             context, testClock, mockSilentFeedback, Optional.of("instance3"), CONTROL_EXECUTOR);
597 
598     assertThat(fileGroupsMetadataAbsent.getGarbageCollectorFile().getAbsolutePath())
599         .isNotEqualTo(fileGroupsMetadata2.getGarbageCollectorFile().getAbsolutePath());
600 
601     assertThat(fileGroupsMetadata2.getGarbageCollectorFile().getAbsolutePath())
602         .isNotEqualTo(fileGroupsMetadata3.getGarbageCollectorFile().getAbsolutePath());
603   }
604 
605   /**
606    * Writes {@link DataFileGroup} into disk. The main purpose of this method is for the convenience
607    * of migration tests. Previously, the file group metadata is stored in DataFileGroup with
608    * extensions. We wanted to make sure that after migrating to {@link DataFileGroupInternal}, the
609    * previous metadata can still be parsed.
610    */
writeDataFileGroup( GroupKey groupKey, DataFileGroup fileGroup, Optional<String> instanceId)611   boolean writeDataFileGroup(
612       GroupKey groupKey, DataFileGroup fileGroup, Optional<String> instanceId) {
613     String serializedGroupKey = FileGroupsMetadataUtil.getSerializedGroupKey(groupKey);
614     SharedPreferences prefs =
615         SharedPreferencesUtil.getSharedPreferences(
616             context, FileGroupsMetadataUtil.MDD_FILE_GROUPS, instanceId);
617     return SharedPreferencesUtil.writeProto(prefs, serializedGroupKey, fileGroup);
618   }
619 
readPendingFileGroupFromSharedPrefs( GroupKey key, boolean shouldExist)620   private DataFileGroupInternal readPendingFileGroupFromSharedPrefs(
621       GroupKey key, boolean shouldExist) throws Exception {
622     GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(false).build();
623     return readFileGroupFromSharedPrefs(duplicateGroupKey, shouldExist);
624   }
625 
writePendingFileGroupToSharedPrefs(GroupKey key, DataFileGroupInternal group)626   private void writePendingFileGroupToSharedPrefs(GroupKey key, DataFileGroupInternal group)
627       throws Exception {
628     GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(false).build();
629     assertThat(fileGroupsMetadata.write(duplicateGroupKey, group).get()).isTrue();
630   }
631 
readDownloadedFileGroupFromSharedPrefs( GroupKey key, boolean shouldExist)632   private DataFileGroupInternal readDownloadedFileGroupFromSharedPrefs(
633       GroupKey key, boolean shouldExist) throws Exception {
634     GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(true).build();
635     return readFileGroupFromSharedPrefs(duplicateGroupKey, shouldExist);
636   }
637 
writeDownloadedFileGroupToSharedPrefs(GroupKey key, DataFileGroupInternal group)638   private void writeDownloadedFileGroupToSharedPrefs(GroupKey key, DataFileGroupInternal group)
639       throws Exception {
640     GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(true).build();
641     assertThat(fileGroupsMetadata.write(duplicateGroupKey, group).get()).isTrue();
642   }
643 
readFileGroupFromSharedPrefs(GroupKey key, boolean shouldExist)644   private DataFileGroupInternal readFileGroupFromSharedPrefs(GroupKey key, boolean shouldExist)
645       throws Exception {
646     DataFileGroupInternal group = fileGroupsMetadata.read(key).get();
647     if (shouldExist) {
648       assertWithMessage(String.format("Expected that key %s should exist.", key))
649           .that(group)
650           .isNotNull();
651     } else {
652       assertWithMessage(String.format("Expected that key %s should not exist.", key))
653           .that(group)
654           .isNull();
655     }
656     return group;
657   }
658 
verifyNoErrorInPdsMigration()659   private void verifyNoErrorInPdsMigration() {}
660 }
661