• 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.logging;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
20 import static org.mockito.ArgumentMatchers.any;
21 import static org.mockito.Mockito.times;
22 import static org.mockito.Mockito.verify;
23 import static org.mockito.Mockito.when;
24 
25 import android.content.Context;
26 import android.net.Uri;
27 import androidx.test.core.app.ApplicationProvider;
28 import com.google.mobiledatadownload.internal.MetadataProto.DataFile;
29 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal;
30 import com.google.mobiledatadownload.internal.MetadataProto.GroupKey;
31 import com.google.mobiledatadownload.internal.MetadataProto.NewFileKey;
32 import com.google.android.libraries.mobiledatadownload.SilentFeedback;
33 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
34 import com.google.android.libraries.mobiledatadownload.file.spi.Backend;
35 import com.google.android.libraries.mobiledatadownload.internal.FileGroupsMetadata;
36 import com.google.android.libraries.mobiledatadownload.internal.MddConstants;
37 import com.google.android.libraries.mobiledatadownload.internal.MddTestUtil;
38 import com.google.android.libraries.mobiledatadownload.internal.SharedFileManager;
39 import com.google.android.libraries.mobiledatadownload.internal.SharedFilesMetadata;
40 import com.google.android.libraries.mobiledatadownload.internal.collect.GroupKeyAndGroup;
41 import com.google.android.libraries.mobiledatadownload.internal.logging.StorageLogger.GroupStorage;
42 import com.google.android.libraries.mobiledatadownload.internal.util.DirectoryUtil;
43 import com.google.android.libraries.mobiledatadownload.testing.TestFlags;
44 import com.google.auto.value.AutoValue;
45 import com.google.common.base.Optional;
46 import com.google.common.util.concurrent.AsyncCallable;
47 import com.google.common.util.concurrent.Futures;
48 import com.google.common.util.concurrent.MoreExecutors;
49 import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats;
50 import com.google.mobiledatadownload.LogProto.MddStorageStats;
51 import java.io.IOException;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.List;
55 import org.junit.Before;
56 import org.junit.Rule;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 import org.mockito.ArgumentCaptor;
60 import org.mockito.Captor;
61 import org.mockito.Mock;
62 import org.mockito.junit.MockitoJUnit;
63 import org.mockito.junit.MockitoRule;
64 import org.robolectric.RobolectricTestRunner;
65 
66 @RunWith(RobolectricTestRunner.class)
67 public class StorageLoggerTest {
68   private static final String GROUP_1 = "group1";
69   private static final String GROUP_2 = "group2";
70   private static final String PACKAGE_1 = "package1";
71   private static final String PACKAGE_2 = "package2";
72   private static final int FILE_GROUP_VERSION_NUMBER_1 = 10;
73   private static final int FILE_GROUP_VERSION_NUMBER_2 = 20;
74 
75   private static final long BUILD_ID_1 = 10;
76   private static final long BUILD_ID_2 = 20;
77   private static final String VARIANT_ID = "test-variant";
78 
79   // Note: We can't make those android uris static variable since the Uri.parse will fail
80   // with initialization.
81   private final Uri androidUri1 =
82       Uri.parse("android://com.google.android.gms/files/datadownload/shared/public/file_1");
83   private static final long FILE_SIZE_1 = 1;
84 
85   private final Uri androidUri2 =
86       Uri.parse("android://com.google.android.gms/files/datadownload/shared/public/file_2");
87   private static final long FILE_SIZE_2 = 2;
88 
89   private final Uri androidUri3 =
90       Uri.parse("android://com.google.android.gms/files/datadownload/shared/public/file_3");
91   private static final long FILE_SIZE_3 = 4;
92 
93   private final Uri androidUri4 =
94       Uri.parse("android://com.google.android.gms/files/datadownload/shared/public/file_4");
95   private static final long FILE_SIZE_4 = 8;
96 
97   private final Uri androidUri5 =
98       Uri.parse("android://com.google.android.gms/files/datadownload/shared/public/file_5");
99   private static final long FILE_SIZE_5 = 16;
100 
101   private final Uri androidUri6 =
102       Uri.parse("android://com.google.android.gms/files/datadownload/shared/public/file_6");
103   private static final long FILE_SIZE_6 = 32;
104 
105   private final Uri inlineUri1 =
106       Uri.parse("android://com.google.android.gms/files/datadownload/shared/public/inline_file_1");
107   private static final long INLINE_FILE_SIZE_1 = 64;
108 
109   private static final long MDD_DIRECTORY_SIZE =
110       FILE_SIZE_1
111           + FILE_SIZE_2
112           + FILE_SIZE_3
113           + FILE_SIZE_4
114           + FILE_SIZE_5
115           + FILE_SIZE_6
116           + INLINE_FILE_SIZE_1;
117 
118   // These files will belong to 2 groups
119   private static final DataFile DATA_FILE_1 = MddTestUtil.createDataFile("file1", 1);
120   private static final DataFile DATA_FILE_2 = MddTestUtil.createDataFile("file2", 2);
121   private static final DataFile DATA_FILE_3 = MddTestUtil.createDataFile("file3", 3);
122   private static final DataFile DATA_FILE_4 = MddTestUtil.createDataFile("file4", 4);
123   private static final DataFile DATA_FILE_5 = MddTestUtil.createDataFile("file5", 5);
124   private static final DataFile DATA_FILE_6 = MddTestUtil.createDataFile("file6", 6);
125   private static final DataFile INLINE_DATA_FILE_1 =
126       DataFile.newBuilder()
127           .setFileId("inlineFile1")
128           .setUrlToDownload("inlinefile:sha1:inlinefile1")
129           .setChecksum("inlinefile1")
130           .setByteSize((int) INLINE_FILE_SIZE_1)
131           .build();
132 
133   private SynchronousFileStorage fileStorage;
134 
135   private final Context context = ApplicationProvider.getApplicationContext();
136 
137   @Rule public final MockitoRule mocks = MockitoJUnit.rule();
138 
139   @Mock EventLogger mockEventLogger;
140   @Mock FileGroupsMetadata mockFileGroupsMetadata;
141   @Mock SharedFileManager mockSharedFileManager;
142   @Mock Backend mockBackend;
143   @Mock SilentFeedback mockSilentFeedback;
144 
145   @Captor ArgumentCaptor<AsyncCallable<MddStorageStats>> mddStorageStatsCallableArgumentCaptor;
146 
147   private final TestFlags flags = new TestFlags();
148 
149   @Before
setUp()150   public void setUp() throws Exception {
151 
152     setUpFileMock(androidUri1, FILE_SIZE_1);
153     setUpFileMock(androidUri2, FILE_SIZE_2);
154     setUpFileMock(androidUri3, FILE_SIZE_3);
155     setUpFileMock(androidUri4, FILE_SIZE_4);
156     setUpFileMock(androidUri5, FILE_SIZE_5);
157     setUpFileMock(androidUri6, FILE_SIZE_6);
158     setUpFileMock(inlineUri1, INLINE_FILE_SIZE_1);
159 
160     Uri downloadDirUri = DirectoryUtil.getBaseDownloadDirectory(context, Optional.absent());
161     setUpDirectoryMock(
162         downloadDirUri,
163         Arrays.asList(
164             androidUri1,
165             androidUri2,
166             androidUri3,
167             androidUri4,
168             androidUri5,
169             androidUri6,
170             inlineUri1));
171 
172     when(mockBackend.name()).thenReturn("android");
173     fileStorage = new SynchronousFileStorage(Arrays.asList(mockBackend));
174 
175     flags.storageStatsLoggingSampleInterval = Optional.of(1);
176   }
177 
178   // TODO(b/115659980): consider moving this to a public utility class in the File Library
setUpFileMock(Uri uri, long size)179   private void setUpFileMock(Uri uri, long size) throws IOException {
180     when(mockBackend.exists(uri)).thenReturn(true);
181     when(mockBackend.isDirectory(uri)).thenReturn(false);
182     when(mockBackend.fileSize(uri)).thenReturn(size);
183   }
184 
185   // TODO(b/115659980): consider moving this to a public utility class in the File Library
setUpDirectoryMock(Uri uri, List<Uri> children)186   private void setUpDirectoryMock(Uri uri, List<Uri> children) throws IOException {
187     when(mockBackend.exists(uri)).thenReturn(true);
188     when(mockBackend.isDirectory(uri)).thenReturn(true);
189     when(mockBackend.children(uri)).thenReturn(children);
190   }
191 
192   @Test
testLogMddStorageStats()193   public void testLogMddStorageStats() throws Exception {
194     // Setup Group1 that has 3 FileDataGroups:
195     // - Stale group has DATA_FILE_1, DATA_FILE_2.
196     // - Downloaded group has DATA_FILE_2, DATA_FILE_3.
197     // - Pending group has DATA_FILE_3, DATA_FILE_4.
198     DataFileGroupInternal group1Stale =
199         createDataFileGroupWithFiles(
200             GROUP_1,
201             PACKAGE_1,
202             Arrays.asList(DATA_FILE_1, DATA_FILE_2),
203             Arrays.asList(androidUri1, androidUri2));
204     DataFileGroupInternal group1Downloaded =
205         createDataFileGroupWithFiles(
206                 GROUP_1,
207                 PACKAGE_1,
208                 Arrays.asList(DATA_FILE_2, DATA_FILE_3),
209                 Arrays.asList(androidUri2, androidUri3))
210             .toBuilder()
211             .setFileGroupVersionNumber(FILE_GROUP_VERSION_NUMBER_1)
212             .setBuildId(BUILD_ID_1)
213             .setVariantId(VARIANT_ID)
214             .build();
215     DataFileGroupInternal group1Pending =
216         createDataFileGroupWithFiles(
217             GROUP_1,
218             PACKAGE_1,
219             Arrays.asList(DATA_FILE_3, DATA_FILE_4),
220             Arrays.asList(androidUri3, androidUri4));
221 
222     // Setup Group2 that has 2 FileDataGroups:
223     // - Downloaded group has DATA_FILE_5.
224     // - Pending group has DATA_FILE_6.
225     DataFileGroupInternal group2Downloaded =
226         createDataFileGroupWithFiles(
227                 GROUP_2, PACKAGE_2, Arrays.asList(DATA_FILE_5), Arrays.asList(androidUri5))
228             .toBuilder()
229             .setFileGroupVersionNumber(FILE_GROUP_VERSION_NUMBER_2)
230             .setBuildId(BUILD_ID_2)
231             .setVariantId(VARIANT_ID)
232             .build();
233     DataFileGroupInternal group2Pending =
234         createDataFileGroupWithFiles(
235             GROUP_2, PACKAGE_2, Arrays.asList(DATA_FILE_6), Arrays.asList(androidUri6));
236 
237     List<GroupKeyAndGroup> groups = new ArrayList<>();
238     groups.add(createGroupKeyAndGroup(group1Downloaded, true /*downloaded*/));
239     groups.add(createGroupKeyAndGroup(group1Pending, false /*downloaded*/));
240     groups.add(createGroupKeyAndGroup(group2Downloaded, true /*downloaded*/));
241     groups.add(createGroupKeyAndGroup(group2Pending, false /*downloaded*/));
242     when(mockFileGroupsMetadata.getAllFreshGroups()).thenReturn(Futures.immediateFuture(groups));
243 
244     when(mockFileGroupsMetadata.getAllStaleGroups())
245         .thenReturn(Futures.immediateFuture(Arrays.asList(group1Stale)));
246 
247     verifyStorageStats(
248         /* totalMddBytesUsed= */ FILE_SIZE_1
249             + FILE_SIZE_2
250             + FILE_SIZE_3
251             + FILE_SIZE_4
252             + FILE_SIZE_5
253             + FILE_SIZE_6,
254         ExpectedFileGroupStorageStats.create(
255             GROUP_1,
256             PACKAGE_1,
257             BUILD_ID_1,
258             VARIANT_ID,
259             FILE_GROUP_VERSION_NUMBER_1,
260             createGroupStorage(
261                 /* totalBytesUsed= */ FILE_SIZE_1 + FILE_SIZE_2 + FILE_SIZE_3 + FILE_SIZE_4,
262                 /* totalInlineBytesUsed= */ 0,
263                 /* downloadedGroupBytesUsed= */ FILE_SIZE_2 + FILE_SIZE_3,
264                 /* downloadedGroupInlineBytesUsed= */ 0,
265                 /* totalFileCount= */ 2,
266                 /* totalInlineFileCount= */ 0)),
267         ExpectedFileGroupStorageStats.create(
268             GROUP_2,
269             PACKAGE_2,
270             BUILD_ID_2,
271             VARIANT_ID,
272             FILE_GROUP_VERSION_NUMBER_2,
273             createGroupStorage(
274                 /* totalBytesUsed= */ FILE_SIZE_5 + FILE_SIZE_6,
275                 /* totalInlineBytesUsed= */ 0,
276                 /* downloadedGroupBytesUsed= */ FILE_SIZE_5,
277                 /* downloadedGroupInlineBytesUsed= */ 0,
278                 /* totalFileCount= */ 1,
279                 /* totalInlineFileCount= */ 0)));
280   }
281 
282   @Test
testLogMddStorageStats_noDownloadedInGroup2()283   public void testLogMddStorageStats_noDownloadedInGroup2() throws Exception {
284     // Setup Group1 that has 3 FileDataGroups:
285     // - Stale group has DATA_FILE_1, DATA_FILE_2.
286     // - Downloaded group has DATA_FILE_2, DATA_FILE_3.
287     // - Pending group has DATA_FILE_3, DATA_FILE_4.
288     DataFileGroupInternal group1Stale =
289         createDataFileGroupWithFiles(
290             GROUP_1,
291             PACKAGE_1,
292             Arrays.asList(DATA_FILE_1, DATA_FILE_2),
293             Arrays.asList(androidUri1, androidUri2));
294     DataFileGroupInternal group1Downloaded =
295         createDataFileGroupWithFiles(
296                 GROUP_1,
297                 PACKAGE_1,
298                 Arrays.asList(DATA_FILE_2, DATA_FILE_3),
299                 Arrays.asList(androidUri2, androidUri3))
300             .toBuilder()
301             .setFileGroupVersionNumber(FILE_GROUP_VERSION_NUMBER_1)
302             .setBuildId(BUILD_ID_1)
303             .setVariantId(VARIANT_ID)
304             .build();
305     DataFileGroupInternal group1Pending =
306         createDataFileGroupWithFiles(
307             GROUP_1,
308             PACKAGE_1,
309             Arrays.asList(DATA_FILE_3, DATA_FILE_4),
310             Arrays.asList(androidUri3, androidUri4));
311 
312     // Setup Group2 that has 2 FileDataGroups (no downloaded)
313     // - Stale group has DATA_FILE_5.
314     // - Pending group has DATA_FILE_6.
315     DataFileGroupInternal group2Stale =
316         createDataFileGroupWithFiles(
317             GROUP_2, PACKAGE_2, Arrays.asList(DATA_FILE_5), Arrays.asList(androidUri5));
318     DataFileGroupInternal group2Pending =
319         createDataFileGroupWithFiles(
320             GROUP_2, PACKAGE_2, Arrays.asList(DATA_FILE_6), Arrays.asList(androidUri6));
321 
322     List<GroupKeyAndGroup> groups = new ArrayList<>();
323     groups.add(createGroupKeyAndGroup(group1Downloaded, true /*downloaded*/));
324     groups.add(createGroupKeyAndGroup(group1Pending, false /*downloaded*/));
325     groups.add(createGroupKeyAndGroup(group2Pending, false /*downloaded*/));
326     when(mockFileGroupsMetadata.getAllFreshGroups()).thenReturn(Futures.immediateFuture(groups));
327 
328     when(mockFileGroupsMetadata.getAllStaleGroups())
329         .thenReturn(Futures.immediateFuture(Arrays.asList(group1Stale, group2Stale)));
330 
331     verifyStorageStats(
332         /* totalMddBytesUsed= */ FILE_SIZE_1
333             + FILE_SIZE_2
334             + FILE_SIZE_3
335             + FILE_SIZE_4
336             + FILE_SIZE_5
337             + FILE_SIZE_6,
338         ExpectedFileGroupStorageStats.create(
339             GROUP_1,
340             PACKAGE_1,
341             BUILD_ID_1,
342             VARIANT_ID,
343             FILE_GROUP_VERSION_NUMBER_1,
344             createGroupStorage(
345                 /* totalBytesUsed= */ FILE_SIZE_1 + FILE_SIZE_2 + FILE_SIZE_3 + FILE_SIZE_4,
346                 /* totalInlineBytesUsed= */ 0,
347                 /* downloadedGroupBytesUsed= */ FILE_SIZE_2 + FILE_SIZE_3,
348                 /* downloadedGroupInlineBytesUsed= */ 0,
349                 /* totalFileCount= */ 2,
350                 /* totalInlineFileCount= */ 0)),
351         ExpectedFileGroupStorageStats.create(
352             GROUP_2,
353             PACKAGE_2,
354             /* buildId= */ 0,
355             /* variantId= */ "",
356             /* fileGroupVersionNumber= */ -1,
357             createGroupStorage(
358                 /* totalBytesUsed= */ FILE_SIZE_5 + FILE_SIZE_6,
359                 /* totalInlineBytesUsed= */ 0,
360                 /* downloadedGroupBytesUsed= */ 0,
361                 /* downloadedGroupInlineBytesUsed= */ 0,
362                 /* totalFileCount= */ 1,
363                 /* totalInlineFileCount= */ 0)));
364   }
365 
366   @Test
testLogMddStorageStats_commonFilesBetweenGroups()367   public void testLogMddStorageStats_commonFilesBetweenGroups() throws Exception {
368     // Setup Group1 that has 3 FileDataGroups:
369     // - Stale group has DATA_FILE_1, DATA_FILE_2.
370     // - Downloaded group has DATA_FILE_2, DATA_FILE_3.
371     // - Pending group has DATA_FILE_3, DATA_FILE_4.
372     DataFileGroupInternal group1Stale =
373         createDataFileGroupWithFiles(
374             GROUP_1,
375             PACKAGE_1,
376             Arrays.asList(DATA_FILE_1, DATA_FILE_2),
377             Arrays.asList(androidUri1, androidUri2));
378     DataFileGroupInternal group1Downloaded =
379         createDataFileGroupWithFiles(
380                 GROUP_1,
381                 PACKAGE_1,
382                 Arrays.asList(DATA_FILE_2, DATA_FILE_3),
383                 Arrays.asList(androidUri2, androidUri3))
384             .toBuilder()
385             .setFileGroupVersionNumber(FILE_GROUP_VERSION_NUMBER_1)
386             .setBuildId(BUILD_ID_1)
387             .setVariantId(VARIANT_ID)
388             .build();
389     DataFileGroupInternal group1Pending =
390         createDataFileGroupWithFiles(
391             GROUP_1,
392             PACKAGE_1,
393             Arrays.asList(DATA_FILE_3, DATA_FILE_4),
394             Arrays.asList(androidUri3, androidUri4));
395 
396     // Setup Group2 that has 3 FileDataGroups:
397     // - Stale group has DATA_FILE_1, DATA_FILE_3.
398     // - Downloaded group has DATA_FILE_4, DATA_FILE_5.
399     // - Pending group has DATA_FILE_6.
400     DataFileGroupInternal group2Stale =
401         createDataFileGroupWithFiles(
402             GROUP_2,
403             PACKAGE_2,
404             Arrays.asList(DATA_FILE_1, DATA_FILE_3),
405             Arrays.asList(androidUri1, androidUri3));
406     DataFileGroupInternal group2Downloaded =
407         createDataFileGroupWithFiles(
408                 GROUP_2,
409                 PACKAGE_2,
410                 Arrays.asList(DATA_FILE_4, DATA_FILE_5),
411                 Arrays.asList(androidUri4, androidUri5))
412             .toBuilder()
413             .setFileGroupVersionNumber(FILE_GROUP_VERSION_NUMBER_2)
414             .setBuildId(BUILD_ID_2)
415             .setVariantId(VARIANT_ID)
416             .build();
417     DataFileGroupInternal group2Pending =
418         createDataFileGroupWithFiles(
419             GROUP_2, PACKAGE_2, Arrays.asList(DATA_FILE_6), Arrays.asList(androidUri6));
420 
421     List<GroupKeyAndGroup> groups = new ArrayList<>();
422     groups.add(createGroupKeyAndGroup(group1Downloaded, true /*downloaded*/));
423     groups.add(createGroupKeyAndGroup(group1Pending, false /*downloaded*/));
424     groups.add(createGroupKeyAndGroup(group2Downloaded, true /*downloaded*/));
425     groups.add(createGroupKeyAndGroup(group2Pending, false /*downloaded*/));
426     when(mockFileGroupsMetadata.getAllFreshGroups()).thenReturn(Futures.immediateFuture(groups));
427 
428     when(mockFileGroupsMetadata.getAllStaleGroups())
429         .thenReturn(Futures.immediateFuture(Arrays.asList(group1Stale, group2Stale)));
430 
431     verifyStorageStats(
432         /* totalMddBytesUsed= */ FILE_SIZE_1
433             + FILE_SIZE_2
434             + FILE_SIZE_3
435             + FILE_SIZE_4
436             + FILE_SIZE_5
437             + FILE_SIZE_6,
438         ExpectedFileGroupStorageStats.create(
439             GROUP_1,
440             PACKAGE_1,
441             BUILD_ID_1,
442             VARIANT_ID,
443             FILE_GROUP_VERSION_NUMBER_1,
444             createGroupStorage(
445                 /* totalBytesUsed= */ FILE_SIZE_1 + FILE_SIZE_2 + FILE_SIZE_3 + FILE_SIZE_4,
446                 /* totalInlineBytesUsed= */ 0,
447                 /* downloadedGroupBytesUsed= */ FILE_SIZE_2 + FILE_SIZE_3,
448                 /* downloadedGroupInlineBytesUsed= */ 0,
449                 /* totalFileCount= */ 2,
450                 /* totalInlineFileCount= */ 0)),
451         ExpectedFileGroupStorageStats.create(
452             GROUP_2,
453             PACKAGE_2,
454             BUILD_ID_2,
455             VARIANT_ID,
456             FILE_GROUP_VERSION_NUMBER_2,
457             createGroupStorage(
458                 /* totalBytesUsed= */ FILE_SIZE_1
459                     + FILE_SIZE_3
460                     + FILE_SIZE_4
461                     + FILE_SIZE_5
462                     + FILE_SIZE_6,
463                 /* totalInlineBytesUsed= */ 0,
464                 /* downloadedGroupBytesUsed= */ FILE_SIZE_4 + FILE_SIZE_5,
465                 /* downloadedGroupInlineBytesUsed= */ 0,
466                 /* totalFileCount= */ 2,
467                 /* totalInlineFileCount= */ 0)));
468   }
469 
470   @Test
testLogMddStorageStats_emptyDownloadedGroup()471   public void testLogMddStorageStats_emptyDownloadedGroup() throws Exception {
472     // Setup Group1 that has 3 FileDataGroups:
473     // - Stale group has DATA_FILE_1, DATA_FILE_2.
474     // - Downloaded group has DATA_FILE_2, DATA_FILE_3.
475     // - Pending group has DATA_FILE_3, DATA_FILE_4.
476     DataFileGroupInternal group1Stale =
477         createDataFileGroupWithFiles(
478             GROUP_1,
479             PACKAGE_1,
480             Arrays.asList(DATA_FILE_1, DATA_FILE_2),
481             Arrays.asList(androidUri1, androidUri2));
482     DataFileGroupInternal group1Downloaded =
483         createDataFileGroupWithFiles(
484                 GROUP_1,
485                 PACKAGE_1,
486                 Arrays.asList(DATA_FILE_2, DATA_FILE_3),
487                 Arrays.asList(androidUri2, androidUri3))
488             .toBuilder()
489             .setFileGroupVersionNumber(FILE_GROUP_VERSION_NUMBER_1)
490             .setBuildId(BUILD_ID_1)
491             .setVariantId(VARIANT_ID)
492             .build();
493     DataFileGroupInternal group1Pending =
494         createDataFileGroupWithFiles(
495             GROUP_1,
496             PACKAGE_1,
497             Arrays.asList(DATA_FILE_3, DATA_FILE_4),
498             Arrays.asList(androidUri3, androidUri4));
499 
500     // Downloaded Group2 is empty (no file). This could happen when we send an empty FileGroup to
501     // clear old config.
502     DataFileGroupInternal group2Downloaded =
503         createDataFileGroupWithFiles(
504                 GROUP_2, PACKAGE_2, new ArrayList<>() /*dataFiles*/, new ArrayList<>() /*fileUris*/)
505             .toBuilder()
506             .setFileGroupVersionNumber(FILE_GROUP_VERSION_NUMBER_2)
507             .setBuildId(BUILD_ID_2)
508             .setVariantId(VARIANT_ID)
509             .build();
510 
511     List<GroupKeyAndGroup> groups = new ArrayList<>();
512     groups.add(createGroupKeyAndGroup(group1Downloaded, true /*downloaded*/));
513     groups.add(createGroupKeyAndGroup(group1Pending, false /*downloaded*/));
514     groups.add(createGroupKeyAndGroup(group2Downloaded, true /*downloaded*/));
515     when(mockFileGroupsMetadata.getAllFreshGroups()).thenReturn(Futures.immediateFuture(groups));
516 
517     when(mockFileGroupsMetadata.getAllStaleGroups())
518         .thenReturn(Futures.immediateFuture(Arrays.asList(group1Stale)));
519 
520     verifyStorageStats(
521         /* totalMddBytesUsed= */ FILE_SIZE_1 + FILE_SIZE_2 + FILE_SIZE_3 + FILE_SIZE_4,
522         ExpectedFileGroupStorageStats.create(
523             GROUP_1,
524             PACKAGE_1,
525             BUILD_ID_1,
526             VARIANT_ID,
527             FILE_GROUP_VERSION_NUMBER_1,
528             createGroupStorage(
529                 /* totalBytesUsed= */ FILE_SIZE_1 + FILE_SIZE_2 + FILE_SIZE_3 + FILE_SIZE_4,
530                 /* totalInlineBytesUsed= */ 0,
531                 /* downloadedGroupBytesUsed= */ FILE_SIZE_2 + FILE_SIZE_3,
532                 /* downloadedGroupInlineBytesUsed= */ 0,
533                 /* totalFileCount= */ 2,
534                 /* totalInlineFileCount= */ 0)),
535         ExpectedFileGroupStorageStats.create(
536             GROUP_2,
537             PACKAGE_2,
538             BUILD_ID_2,
539             VARIANT_ID,
540             FILE_GROUP_VERSION_NUMBER_2,
541             createGroupStorage(
542                 /* totalBytesUsed= */ 0,
543                 /* totalInlineBytesUsed= */ 0,
544                 /* downloadedGroupBytesUsed= */ 0,
545                 /* downloadedGroupInlineBytesUsed= */ 0,
546                 /* totalFileCount= */ 0,
547                 /* totalInlineFileCount= */ 0)));
548   }
549 
550   @Test
testLogMddStorageStats_mddDirectoryNotExists()551   public void testLogMddStorageStats_mddDirectoryNotExists() throws Exception {
552     when(mockFileGroupsMetadata.getAllFreshGroups())
553         .thenReturn(Futures.immediateFuture(new ArrayList<>()));
554     when(mockFileGroupsMetadata.getAllStaleGroups())
555         .thenReturn(Futures.immediateFuture(new ArrayList<>()));
556     when(mockBackend.exists(DirectoryUtil.getBaseDownloadDirectory(context, Optional.absent())))
557         .thenReturn(false);
558 
559     StorageLogger storageLogger =
560         new StorageLogger(
561             context,
562             mockFileGroupsMetadata,
563             mockSharedFileManager,
564             fileStorage,
565             mockEventLogger,
566             mockSilentFeedback,
567             Optional.absent(),
568             MoreExecutors.directExecutor());
569 
570     when(mockEventLogger.logMddStorageStats(any())).thenReturn(immediateVoidFuture());
571 
572     storageLogger.logStorageStats(/* daysSinceLastLog= */ 1).get();
573 
574     verify(mockEventLogger, times(1))
575         .logMddStorageStats(mddStorageStatsCallableArgumentCaptor.capture());
576     AsyncCallable<MddStorageStats> mddStorageStatsCallable =
577         mddStorageStatsCallableArgumentCaptor.getValue();
578 
579     MddStorageStats mddStorageStats = mddStorageStatsCallable.call().get();
580     assertThat(mddStorageStats.getTotalMddBytesUsed()).isEqualTo(0);
581     assertThat(mddStorageStats.getTotalMddDirectoryBytesUsed()).isEqualTo(0);
582 
583     assertThat(mddStorageStats.getDataDownloadFileGroupStatsList()).isEmpty();
584     assertThat(mddStorageStats.getTotalBytesUsedList()).isEmpty();
585     assertThat(mddStorageStats.getDownloadedGroupBytesUsedList()).isEmpty();
586   }
587 
588   @Test
testMddStorageStats_includesDaysSinceLastLog()589   public void testMddStorageStats_includesDaysSinceLastLog() throws Exception {
590     when(mockFileGroupsMetadata.getAllFreshGroups())
591         .thenReturn(Futures.immediateFuture(new ArrayList<>()));
592     when(mockFileGroupsMetadata.getAllStaleGroups())
593         .thenReturn(Futures.immediateFuture(new ArrayList<>()));
594     when(mockBackend.exists(DirectoryUtil.getBaseDownloadDirectory(context, Optional.absent())))
595         .thenReturn(false);
596 
597     StorageLogger storageLogger =
598         new StorageLogger(
599             context,
600             mockFileGroupsMetadata,
601             mockSharedFileManager,
602             fileStorage,
603             mockEventLogger,
604             mockSilentFeedback,
605             Optional.absent(),
606             MoreExecutors.directExecutor());
607 
608     when(mockEventLogger.logMddStorageStats(any())).thenReturn(immediateVoidFuture());
609 
610     storageLogger.logStorageStats(/* daysSinceLastLog= */ -1).get();
611 
612     verify(mockEventLogger, times(1))
613         .logMddStorageStats(mddStorageStatsCallableArgumentCaptor.capture());
614 
615     AsyncCallable<MddStorageStats> mddStorageStatsCallable =
616         mddStorageStatsCallableArgumentCaptor.getValue();
617     MddStorageStats mddStorageStats = mddStorageStatsCallable.call().get();
618 
619     assertThat(mddStorageStats.getDaysSinceLastLog()).isEqualTo(-1);
620   }
621 
622   @Test
testLogMddStorageStats_groupWithInlineFiles()623   public void testLogMddStorageStats_groupWithInlineFiles() throws Exception {
624     // Setup Group1 that has 3 FileDataGroups:
625     // - Stale group has DATA_FILE_1, DATA_FILE_2.
626     // - Downloaded group has DATA_FILE_2, INLINE_FILE_1,
627     // - Pending group has DATA_FILE_3, INLINE_FILE_1,
628     DataFileGroupInternal group1Stale =
629         createDataFileGroupWithFiles(
630                 GROUP_1,
631                 PACKAGE_1,
632                 Arrays.asList(DATA_FILE_1, DATA_FILE_2),
633                 Arrays.asList(androidUri1, androidUri2))
634             .toBuilder()
635             .setFileGroupVersionNumber(FILE_GROUP_VERSION_NUMBER_1)
636             .setBuildId(BUILD_ID_1)
637             .setVariantId(VARIANT_ID)
638             .build();
639     DataFileGroupInternal group1Downloaded =
640         createDataFileGroupWithFiles(
641                 GROUP_1,
642                 PACKAGE_1,
643                 Arrays.asList(DATA_FILE_2, INLINE_DATA_FILE_1),
644                 Arrays.asList(androidUri2, inlineUri1))
645             .toBuilder()
646             .setFileGroupVersionNumber(FILE_GROUP_VERSION_NUMBER_1)
647             .setBuildId(BUILD_ID_1)
648             .setVariantId(VARIANT_ID)
649             .build();
650     DataFileGroupInternal group1Pending =
651         createDataFileGroupWithFiles(
652                 GROUP_1,
653                 PACKAGE_1,
654                 Arrays.asList(DATA_FILE_3, INLINE_DATA_FILE_1),
655                 Arrays.asList(androidUri3, inlineUri1))
656             .toBuilder()
657             .setFileGroupVersionNumber(FILE_GROUP_VERSION_NUMBER_1)
658             .setBuildId(BUILD_ID_1)
659             .setVariantId(VARIANT_ID)
660             .build();
661 
662     List<GroupKeyAndGroup> groups = new ArrayList<>();
663     groups.add(createGroupKeyAndGroup(group1Downloaded, true /*downloaded*/));
664     groups.add(createGroupKeyAndGroup(group1Pending, false /*downloaded*/));
665     when(mockFileGroupsMetadata.getAllFreshGroups()).thenReturn(Futures.immediateFuture(groups));
666 
667     when(mockFileGroupsMetadata.getAllStaleGroups())
668         .thenReturn(Futures.immediateFuture(Arrays.asList(group1Stale)));
669 
670     verifyStorageStats(
671         /* totalMddBytesUsed= */ FILE_SIZE_1 + FILE_SIZE_2 + FILE_SIZE_3 + INLINE_FILE_SIZE_1,
672         ExpectedFileGroupStorageStats.create(
673             GROUP_1,
674             PACKAGE_1,
675             BUILD_ID_1,
676             VARIANT_ID,
677             FILE_GROUP_VERSION_NUMBER_1,
678             createGroupStorage(
679                 /* totalBytesUsed= */ FILE_SIZE_1 + FILE_SIZE_2 + FILE_SIZE_3 + INLINE_FILE_SIZE_1,
680                 /* totalInlineBytesUsed= */ INLINE_FILE_SIZE_1,
681                 /* downloadedGroupBytesUsed= */ FILE_SIZE_2 + INLINE_FILE_SIZE_1,
682                 /* downloadedGroupInlineBytesUsed= */ INLINE_FILE_SIZE_1,
683                 /* totalFileCount= */ 2,
684                 /* totalInlineFileCount= */ 1)));
685   }
686 
verifyStorageStats( long totalMddBytesUsed, ExpectedFileGroupStorageStats... expectedStatsList)687   private void verifyStorageStats(
688       long totalMddBytesUsed, ExpectedFileGroupStorageStats... expectedStatsList) throws Exception {
689     StorageLogger storageLogger =
690         new StorageLogger(
691             context,
692             mockFileGroupsMetadata,
693             mockSharedFileManager,
694             fileStorage,
695             mockEventLogger,
696             mockSilentFeedback,
697             Optional.absent(),
698             MoreExecutors.directExecutor());
699     when(mockEventLogger.logMddStorageStats(any())).thenReturn(immediateVoidFuture());
700     storageLogger.logStorageStats(/* daysSinceLastLog= */ 1).get();
701 
702     verify(mockEventLogger, times(1))
703         .logMddStorageStats(mddStorageStatsCallableArgumentCaptor.capture());
704 
705     AsyncCallable<MddStorageStats> mddStorageStatsCallable =
706         mddStorageStatsCallableArgumentCaptor.getValue();
707     MddStorageStats mddStorageStats = mddStorageStatsCallable.call().get();
708 
709     assertThat(mddStorageStats.getTotalMddBytesUsed()).isEqualTo(totalMddBytesUsed);
710     assertThat(mddStorageStats.getTotalMddDirectoryBytesUsed()).isEqualTo(MDD_DIRECTORY_SIZE);
711 
712     assertThat(mddStorageStats.getDataDownloadFileGroupStatsCount())
713         .isEqualTo(expectedStatsList.length);
714     assertThat(mddStorageStats.getTotalBytesUsedCount()).isEqualTo(expectedStatsList.length);
715     assertThat(mddStorageStats.getDownloadedGroupBytesUsedCount())
716         .isEqualTo(expectedStatsList.length);
717 
718     for (int i = 0; i < expectedStatsList.length; i++) {
719       DataDownloadFileGroupStats fileGroupStats =
720           mddStorageStats.getDataDownloadFileGroupStatsList().get(i);
721       long totalBytesUsed = mddStorageStats.getTotalBytesUsed(i);
722       long totalInlineBytesUsed = mddStorageStats.getTotalInlineBytesUsed(i);
723       long downloadedGroupBytesUsed = mddStorageStats.getDownloadedGroupBytesUsed(i);
724       long downloadedGroupInlineBytesUsed = mddStorageStats.getDownloadedGroupInlineBytesUsed(i);
725 
726       ExpectedFileGroupStorageStats expectedStats =
727           getExpectedStatsForName(fileGroupStats.getFileGroupName(), expectedStatsList);
728       GroupStorage expectedGroupStorage = expectedStats.groupStorage();
729 
730       assertThat(fileGroupStats.getOwnerPackage()).isEqualTo(expectedStats.packageName());
731       assertThat(fileGroupStats.getFileGroupVersionNumber())
732           .isEqualTo(expectedStats.fileGroupVersionNumber());
733       assertThat(fileGroupStats.getVariantId()).isEqualTo(expectedStats.variantId());
734       assertThat(fileGroupStats.getBuildId()).isEqualTo(expectedStats.buildId());
735       assertThat(totalBytesUsed).isEqualTo(expectedGroupStorage.totalBytesUsed);
736       assertThat(totalInlineBytesUsed).isEqualTo(expectedGroupStorage.totalInlineBytesUsed);
737       assertThat(downloadedGroupBytesUsed).isEqualTo(expectedGroupStorage.downloadedGroupBytesUsed);
738       assertThat(downloadedGroupInlineBytesUsed)
739           .isEqualTo(expectedGroupStorage.downloadedGroupInlineBytesUsed);
740       assertThat(fileGroupStats.getFileCount()).isEqualTo(expectedGroupStorage.totalFileCount);
741       assertThat(fileGroupStats.getInlineFileCount())
742           .isEqualTo(expectedGroupStorage.totalInlineFileCount);
743     }
744   }
745 
746   /** Find the expected stats for a given group name. */
getExpectedStatsForName( String groupName, ExpectedFileGroupStorageStats[] expectedStatsList)747   private ExpectedFileGroupStorageStats getExpectedStatsForName(
748       String groupName, ExpectedFileGroupStorageStats[] expectedStatsList) {
749     for (int i = 0; i < expectedStatsList.length; i++) {
750       if (groupName.equals(expectedStatsList[i].groupName())) {
751         return expectedStatsList[i];
752       }
753     }
754 
755     throw new AssertionError(String.format("Couldn't find group for name: %s", groupName));
756   }
757 
758   /** Creates a data file group with the given list of files. */
createDataFileGroupWithFiles( String fileGroupName, String ownerPackage, List<DataFile> dataFiles, List<Uri> fileUris)759   private DataFileGroupInternal createDataFileGroupWithFiles(
760       String fileGroupName, String ownerPackage, List<DataFile> dataFiles, List<Uri> fileUris) {
761     DataFileGroupInternal.Builder dataFileGroup =
762         DataFileGroupInternal.newBuilder()
763             .setGroupName(fileGroupName)
764             .setOwnerPackage(ownerPackage);
765 
766     for (int i = 0; i < dataFiles.size(); ++i) {
767       DataFile file = dataFiles.get(i);
768       NewFileKey newFileKey =
769           SharedFilesMetadata.createKeyFromDataFile(file, dataFileGroup.getAllowedReadersEnum());
770       dataFileGroup.addFile(file);
771       when(mockSharedFileManager.getOnDeviceUri(newFileKey))
772           .thenReturn(Futures.immediateFuture(fileUris.get(i)));
773     }
774     return dataFileGroup.build();
775   }
776 
createGroupKeyAndGroup( DataFileGroupInternal fileGroup, boolean downloaded)777   private static GroupKeyAndGroup createGroupKeyAndGroup(
778       DataFileGroupInternal fileGroup, boolean downloaded) {
779     GroupKey groupKey = createGroupKey(fileGroup, downloaded);
780     return GroupKeyAndGroup.create(groupKey, fileGroup);
781   }
782 
createGroupKey(DataFileGroupInternal fileGroup, boolean downloaded)783   private static GroupKey createGroupKey(DataFileGroupInternal fileGroup, boolean downloaded) {
784     GroupKey.Builder groupKey = GroupKey.newBuilder().setGroupName(fileGroup.getGroupName());
785 
786     if (fileGroup.getOwnerPackage().isEmpty()) {
787       groupKey.setOwnerPackage(MddConstants.GMS_PACKAGE);
788     } else {
789       groupKey.setOwnerPackage(fileGroup.getOwnerPackage());
790     }
791     groupKey.setDownloaded(downloaded);
792 
793     return groupKey.build();
794   }
795 
createGroupStorage( long totalBytesUsed, long totalInlineBytesUsed, long downloadedGroupBytesUsed, long downloadedGroupInlineBytesUsed, int totalFileCount, int totalInlineFileCount)796   private static GroupStorage createGroupStorage(
797       long totalBytesUsed,
798       long totalInlineBytesUsed,
799       long downloadedGroupBytesUsed,
800       long downloadedGroupInlineBytesUsed,
801       int totalFileCount,
802       int totalInlineFileCount) {
803     GroupStorage groupStorage = new GroupStorage();
804     groupStorage.totalBytesUsed = totalBytesUsed;
805     groupStorage.totalInlineBytesUsed = totalInlineBytesUsed;
806     groupStorage.downloadedGroupBytesUsed = downloadedGroupBytesUsed;
807     groupStorage.downloadedGroupInlineBytesUsed = downloadedGroupInlineBytesUsed;
808     groupStorage.totalFileCount = totalFileCount;
809     groupStorage.totalInlineFileCount = totalInlineFileCount;
810     return groupStorage;
811   }
812 
813   @AutoValue
814   abstract static class ExpectedFileGroupStorageStats {
groupName()815     abstract String groupName();
816 
packageName()817     abstract String packageName();
818 
buildId()819     abstract long buildId();
820 
variantId()821     abstract String variantId();
822 
fileGroupVersionNumber()823     abstract int fileGroupVersionNumber();
824 
groupStorage()825     abstract GroupStorage groupStorage();
826 
create( String groupName, String packageName, long buildId, String variantId, int fileGroupVersionNumber, GroupStorage groupStorage)827     static ExpectedFileGroupStorageStats create(
828         String groupName,
829         String packageName,
830         long buildId,
831         String variantId,
832         int fileGroupVersionNumber,
833         GroupStorage groupStorage) {
834       return new AutoValue_StorageLoggerTest_ExpectedFileGroupStorageStats(
835           groupName, packageName, buildId, variantId, fileGroupVersionNumber, groupStorage);
836     }
837   }
838 }
839