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