1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.watchdog;
18 
19 import static android.car.watchdog.PackageKillableState.KILLABLE_STATE_NEVER;
20 import static android.car.watchdog.PackageKillableState.KILLABLE_STATE_NO;
21 import static android.car.watchdog.PackageKillableState.KILLABLE_STATE_YES;
22 
23 import static com.android.car.watchdog.WatchdogStorage.RETENTION_PERIOD;
24 import static com.android.car.watchdog.WatchdogStorage.STATS_TEMPORAL_UNIT;
25 import static com.android.car.watchdog.WatchdogStorage.WatchdogDbHelper.DATABASE_NAME;
26 
27 import static com.google.common.truth.Truth.assertThat;
28 import static com.google.common.truth.Truth.assertWithMessage;
29 
30 import android.automotive.watchdog.PerStateBytes;
31 import android.car.watchdog.IoOveruseStats;
32 import android.content.Context;
33 import android.database.Cursor;
34 import android.database.sqlite.SQLiteDatabase;
35 import android.util.Slog;
36 import android.util.SparseArray;
37 
38 import androidx.test.InstrumentationRegistry;
39 import androidx.test.filters.SmallTest;
40 import androidx.test.runner.AndroidJUnit4;
41 
42 import org.junit.After;
43 import org.junit.Before;
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 
47 import java.io.File;
48 import java.time.Instant;
49 import java.time.ZonedDateTime;
50 import java.time.temporal.ChronoUnit;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.Collections;
54 import java.util.List;
55 
56 /**
57  * <p>This class contains unit tests for the {@link WatchdogStorage}.
58  */
59 @SmallTest
60 @RunWith(AndroidJUnit4.class)
61 public final class WatchdogStorageUnitTest {
62     private static final String TAG = WatchdogStorageUnitTest.class.getSimpleName();
63 
64     private Context mContext;
65     private WatchdogStorage mService;
66     private File mDatabaseFile;
67 
68     private final TestTimeSource mTimeSource = new TestTimeSource();
69 
70     @Before
setUp()71     public void setUp() throws Exception {
72         mTimeSource.updateNow(/* numDaysAgo= */ 0);
73         mContext = InstrumentationRegistry.getTargetContext().createDeviceProtectedStorageContext();
74         mDatabaseFile = mContext.createDeviceProtectedStorageContext()
75                 .getDatabasePath(DATABASE_NAME);
76         mService = new WatchdogStorage(mContext, /* useDataSystemCarDir= */ false, mTimeSource);
77     }
78 
79     @After
tearDown()80     public void tearDown() {
81         mService.release();
82         if (!mDatabaseFile.delete()) {
83             Slog.e(TAG, "Failed to delete the database file: " + mDatabaseFile.getAbsolutePath());
84         }
85     }
86 
87     @Test
testSaveUserPackageSettings()88     public void testSaveUserPackageSettings() throws Exception {
89         List<WatchdogStorage.UserPackageSettingsEntry> expected = sampleSettings();
90 
91         assertThat(mService.saveUserPackageSettings(expected)).isTrue();
92 
93         assertWithMessage("User package settings").that(mService.getUserPackageSettings())
94                 .containsExactlyElementsIn(expected);
95     }
96 
97     @Test
testOverwriteUserPackageSettings()98     public void testOverwriteUserPackageSettings() throws Exception {
99         List<WatchdogStorage.UserPackageSettingsEntry> expected = Arrays.asList(
100                 new WatchdogStorage.UserPackageSettingsEntry(
101                         /* userId= */ 100, "system_package.non_critical.A", KILLABLE_STATE_YES),
102                 new WatchdogStorage.UserPackageSettingsEntry(
103                         /* userId= */ 100, "system_package.non_critical.B", KILLABLE_STATE_NO));
104 
105         assertThat(mService.saveUserPackageSettings(expected)).isTrue();
106 
107         expected = Arrays.asList(
108                 new WatchdogStorage.UserPackageSettingsEntry(
109                         /* userId= */ 100, "system_package.non_critical.A", KILLABLE_STATE_NEVER),
110                 new WatchdogStorage.UserPackageSettingsEntry(
111                         /* userId= */ 100, "system_package.non_critical.B", KILLABLE_STATE_NO));
112 
113         assertThat(mService.saveUserPackageSettings(expected)).isTrue();
114 
115         assertWithMessage("User package settings after overwrite")
116                 .that(mService.getUserPackageSettings()).containsExactlyElementsIn(expected);
117     }
118 
119     @Test
testSaveAndGetIoOveruseStats()120     public void testSaveAndGetIoOveruseStats() throws Exception {
121         injectSampleUserPackageSettings();
122         /* Start time aligned to the beginning of the day. */
123         long startTime = mTimeSource.getCurrentDate().toEpochSecond();
124 
125         assertWithMessage("Saved I/O usage stats successfully")
126                 .that(mService.saveIoUsageStats(sampleStatsForDate(startTime, /* duration= */ 60)))
127                 .isTrue();
128 
129         long expectedDuration = mTimeSource.getCurrentDateTime().toEpochSecond() - startTime;
130         List<WatchdogStorage.IoUsageStatsEntry> expected = sampleStatsForDate(
131                 startTime, expectedDuration);
132 
133         IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
134                 .containsExactlyElementsIn(expected);
135     }
136 
137     @Test
testSaveAndGetIoOveruseStatsWithOffsettedStartTime()138     public void testSaveAndGetIoOveruseStatsWithOffsettedStartTime() throws Exception {
139         injectSampleUserPackageSettings();
140         /* Start time in the middle of the day. */
141         long startTime = mTimeSource.getCurrentDate().plusHours(12).toEpochSecond();
142         List<WatchdogStorage.IoUsageStatsEntry> entries = sampleStatsForDate(
143                 startTime, /* duration= */ 60);
144 
145         assertWithMessage("Saved I/O usage stats successfully")
146                 .that(mService.saveIoUsageStats(entries)).isTrue();
147 
148         long expectedStartTime = mTimeSource.getCurrentDate().toEpochSecond();
149         long expectedDuration =
150                 mTimeSource.getCurrentDateTime().toEpochSecond() - expectedStartTime;
151         List<WatchdogStorage.IoUsageStatsEntry> expected = sampleStatsForDate(
152                 expectedStartTime, expectedDuration);
153 
154         IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
155                 .containsExactlyElementsIn(expected);
156     }
157 
158     @Test
testOverwriteIoOveruseStats()159     public void testOverwriteIoOveruseStats() throws Exception {
160         injectSampleUserPackageSettings();
161         long startTime = mTimeSource.getCurrentDate().toEpochSecond();
162         long duration = mTimeSource.getCurrentDateTime().toEpochSecond() - startTime;
163 
164         List<WatchdogStorage.IoUsageStatsEntry> statsBeforeOverwrite = Collections.singletonList(
165                 constructIoUsageStatsEntry(
166                         /* userId= */ 100, "system_package.non_critical.A", startTime, duration,
167                         /* remainingWriteBytes= */
168                         CarWatchdogServiceUnitTest.constructPerStateBytes(200, 300, 400),
169                         /* writtenBytes= */
170                         CarWatchdogServiceUnitTest.constructPerStateBytes(1000, 2000, 3000),
171                         /* forgivenWriteBytes= */
172                         CarWatchdogServiceUnitTest.constructPerStateBytes(100, 100, 100),
173                         /* totalOveruses= */ 2, /* forgivenOveruses= */ 0,
174                         /* totalTimesKilled= */ 1));
175 
176         assertWithMessage("Saved I/O usage stats successfully")
177                 .that(mService.saveIoUsageStats(statsBeforeOverwrite)).isTrue();
178 
179         IoUsageStatsEntrySubject.assertWithMessage(
180                 mService.getTodayIoUsageStats(), "I/O usage stats fetched from database")
181                 .containsExactlyElementsIn(statsBeforeOverwrite);
182 
183         List<WatchdogStorage.IoUsageStatsEntry> statsAfterOverwrite = Collections.singletonList(
184                 constructIoUsageStatsEntry(
185                         /* userId= */ 100, "system_package.non_critical.A", startTime, duration,
186                         /* remainingWriteBytes= */
187                         CarWatchdogServiceUnitTest.constructPerStateBytes(400, 600, 800),
188                         /* writtenBytes= */
189                         CarWatchdogServiceUnitTest.constructPerStateBytes(2000, 3000, 4000),
190                         /* forgivenWriteBytes= */
191                         CarWatchdogServiceUnitTest.constructPerStateBytes(1200, 2300, 3400),
192                         /* totalOveruses= */ 4, /* forgivenOveruses= */ 2,
193                         /* totalTimesKilled= */ 2));
194 
195         assertWithMessage("Saved I/O usage stats successfully")
196                 .that(mService.saveIoUsageStats(statsAfterOverwrite)).isTrue();
197 
198         IoUsageStatsEntrySubject.assertWithMessage(
199                 mService.getTodayIoUsageStats(), "Cached in memory I/O usage stats")
200                 .containsExactlyElementsIn(statsBeforeOverwrite);
201 
202         mService.release();
203         mTimeSource.updateNow(/* numDaysAgo= */ 0);
204         mService = new WatchdogStorage(mContext, /* useDataSystemCarDir= */ false, mTimeSource);
205 
206         assertWithMessage("User packages settings").that(mService.getUserPackageSettings())
207                 .isNotEmpty();
208 
209         IoUsageStatsEntrySubject.assertWithMessage(mService.getTodayIoUsageStats(),
210                 "I/O usage stats fetched from database after restart")
211                 .containsExactlyElementsIn(statsAfterOverwrite);
212     }
213 
214     @Test
testSaveIoOveruseStatsOutsideRetentionPeriod()215     public void testSaveIoOveruseStatsOutsideRetentionPeriod() throws Exception {
216         injectSampleUserPackageSettings();
217         int retentionDaysAgo = RETENTION_PERIOD.getDays();
218 
219         assertWithMessage("Saved I/O usage stats successfully")
220                 .that(mService.saveIoUsageStats(sampleStatsBetweenDates(
221                         /* includingStartDaysAgo= */ retentionDaysAgo,
222                         /* excludingEndDaysAgo= */ retentionDaysAgo + 1))).isTrue();
223 
224         assertWithMessage("Didn't fetch I/O overuse stats outside retention period")
225                 .that(mService.getHistoricalIoOveruseStats(
226                         /* userId= */ 100, "system_package.non_critical.A", retentionDaysAgo))
227                 .isNull();
228     }
229 
230     @Test
testGetHistoricalIoOveruseStats()231     public void testGetHistoricalIoOveruseStats() throws Exception {
232         List<WatchdogStorage.UserPackageSettingsEntry> userPackageSettingEntries = sampleSettings();
233 
234         for (int i = 4; i >= 0; i--) {
235             mTimeSource.updateNow(/* numDaysAgo= */ i);
236             // When writing settings and stats for the previous days, mock the behaviour of
237             // the caller to ensure the settings and stats are retrievable after multiple days.
238             assertWithMessage("Save user package settings for " + mTimeSource)
239                     .that(mService.saveUserPackageSettings(userPackageSettingEntries)).isTrue();
240             assertWithMessage("Save I/O usage stats for " + mTimeSource)
241                     .that(mService.saveIoUsageStats(sampleStatsForToday())).isTrue();
242             mService.release();
243             mService = new WatchdogStorage(mContext, /* useDataSystemCarDir= */ false, mTimeSource);
244             assertWithMessage("User package settings for " + mTimeSource)
245                     .that(mService.getUserPackageSettings()).isNotNull();
246         }
247         mTimeSource.updateNow(/* numDaysAgo= */ 0);
248 
249         IoOveruseStats actual  = mService.getHistoricalIoOveruseStats(
250                 /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 7);
251 
252         assertWithMessage("Historical I/O overuse stats for the past week").that(actual)
253                 .isNotNull();
254 
255         // Returned stats shouldn't include stats for the current date as WatchdogPerfHandler fills
256         // the current day's stats.
257         ZonedDateTime currentDate = mTimeSource.getCurrentDate();
258         long startTime = currentDate.minus(4, STATS_TEMPORAL_UNIT).toEpochSecond();
259         long duration = currentDate.toEpochSecond() - startTime;
260         IoOveruseStats expected = new IoOveruseStats.Builder(startTime, duration)
261                 .setTotalOveruses(8).setTotalTimesKilled(4).setTotalBytesWritten(25_200).build();
262 
263         IoOveruseStatsSubject.assertWithMessage(
264                 "Fetched stats only for 4 days. Expected stats (%s) equals actual stats (%s)",
265                 expected.toString(), actual.toString()).that(actual)
266                 .isEqualTo(expected);
267     }
268 
269     @Test
testGetHistoricalIoOveruseStatsWithNoRecentStats()270     public void testGetHistoricalIoOveruseStatsWithNoRecentStats() throws Exception {
271         List<WatchdogStorage.UserPackageSettingsEntry> userPackageSettingEntries = sampleSettings();
272 
273         for (int i = 4; i >= 3; i--) {
274             mTimeSource.updateNow(/* numDaysAgo= */ i);
275             // When writing settings and stats for the previous days, mock the behaviour of
276             // the caller to ensure the settings and stats are retrievable after multiple days.
277             assertWithMessage("Save user package settings for " + mTimeSource)
278                     .that(mService.saveUserPackageSettings(userPackageSettingEntries)).isTrue();
279             assertWithMessage("Save I/O usage stats for " + mTimeSource)
280                     .that(mService.saveIoUsageStats(sampleStatsForToday())).isTrue();
281             mService.release();
282             mService = new WatchdogStorage(mContext, /* useDataSystemCarDir= */ false, mTimeSource);
283             assertWithMessage("User package settings for " + mTimeSource)
284                     .that(mService.getUserPackageSettings()).isNotNull();
285         }
286         mTimeSource.updateNow(/* numDaysAgo= */ 0);
287 
288         IoOveruseStats actual  = mService.getHistoricalIoOveruseStats(
289                 /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 7);
290 
291         assertWithMessage("Historical I/O overuse stats for the past week").that(actual)
292                 .isNotNull();
293 
294         // Returned stats shouldn't include stats for the current date as WatchdogPerfHandler fills
295         // the current day's stats.
296         ZonedDateTime currentDate = mTimeSource.getCurrentDate();
297         long startTime = currentDate.minus(4, STATS_TEMPORAL_UNIT).toEpochSecond();
298         long duration = currentDate.toEpochSecond() - startTime;
299         IoOveruseStats expected = new IoOveruseStats.Builder(startTime, duration)
300                 .setTotalOveruses(4).setTotalTimesKilled(2).setTotalBytesWritten(12_600).build();
301 
302         IoOveruseStatsSubject.assertWithMessage(
303                 "Fetched stats only for 2 days. Expected stats (%s) equals actual stats (%s)",
304                 expected.toString(), actual.toString()).that(actual)
305                 .isEqualTo(expected);
306     }
307 
308     @Test
testGetDailySystemIoUsageSummaries()309     public void testGetDailySystemIoUsageSummaries() throws Exception {
310         injectSampleUserPackageSettings();
311         List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
312         for (int i = 1; i <= 30; ++i) {
313             entries.addAll(sampleStatsBetweenDates(/* includingStartDaysAgo= */ i,
314                     /* excludingEndDaysAgo= */ i + 1, /* writtenBytesMultiplier= */ i));
315         }
316 
317         assertWithMessage("Save I/O usage stats").that(mService.saveIoUsageStats(entries)).isTrue();
318 
319         ZonedDateTime currentDate = mTimeSource.getCurrentDate();
320 
321         List<AtomsProto.CarWatchdogDailyIoUsageSummary> actual =
322                 mService.getDailySystemIoUsageSummaries(
323                         /* includingStartEpochSeconds= */ currentDate.minusDays(15).toEpochSecond(),
324                         /* excludingEndEpochSeconds= */ currentDate.minusDays(7).toEpochSecond());
325 
326         List<AtomsProto.CarWatchdogDailyIoUsageSummary> expected = new ArrayList<>();
327         for (int i = 15; i > 7; --i) {
328             expected.add(CarWatchdogServiceUnitTest
329                     .constructCarWatchdogDailyIoUsageSummary(/* fgWrBytes= */ 10402L * i,
330                             /* bgWrBytes= */ 14402L * i, /* gmWrBytes= */ 18402L * i,
331                             /* overuseCount= */ 6));
332         }
333 
334         assertWithMessage("Daily system I/O usage summary stats").that(actual).isEqualTo(expected);
335     }
336 
337     @Test
testGetDailySystemIoUsageSummariesWithoutStats()338     public void testGetDailySystemIoUsageSummariesWithoutStats() throws Exception {
339         injectSampleUserPackageSettings();
340         List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
341         for (int i = 1; i <= 7; ++i) {
342             entries.addAll(sampleStatsBetweenDates(/* includingStartDaysAgo= */ i,
343                     /* excludingEndDaysAgo= */ i + 1, /* writtenBytesMultiplier= */ i));
344         }
345 
346         assertWithMessage("Save I/O usage stats").that(mService.saveIoUsageStats(entries)).isTrue();
347 
348         ZonedDateTime currentDate = mTimeSource.getCurrentDate();
349 
350         List<AtomsProto.CarWatchdogDailyIoUsageSummary> actual =
351                 mService.getDailySystemIoUsageSummaries(
352                         /* includingStartEpochSeconds= */ currentDate.minusDays(15).toEpochSecond(),
353                         /* excludingEndEpochSeconds= */ currentDate.minusDays(7).toEpochSecond());
354 
355         assertWithMessage("Daily system I/O usage summary stats").that(actual).isNull();
356     }
357 
358     @Test
testGetTopUsersDailyIoUsageSummaries()359     public void testGetTopUsersDailyIoUsageSummaries() throws Exception {
360         injectSampleUserPackageSettings();
361         List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
362         for (int i = 1; i <= 30; ++i) {
363             entries.addAll(sampleStatsBetweenDates(/* includingStartDaysAgo= */ i,
364                     /* excludingEndDaysAgo= */ i + 1, /* writtenBytesMultiplier= */ i));
365         }
366 
367         assertWithMessage("Save I/O usage stats").that(mService.saveIoUsageStats(entries)).isTrue();
368 
369         ZonedDateTime currentDate = mTimeSource.getCurrentDate();
370 
371         List<WatchdogStorage.UserPackageDailySummaries> actual =
372                 mService.getTopUsersDailyIoUsageSummaries(/* numTopUsers= */ 3,
373                         /* minSystemTotalWrittenBytes= */ 600_000,
374                         /* includingStartEpochSeconds= */ currentDate.minusDays(15).toEpochSecond(),
375                         /* excludingEndEpochSeconds= */ currentDate.minusDays(7).toEpochSecond());
376 
377         List<AtomsProto.CarWatchdogDailyIoUsageSummary> user101VendorPkgSummaries =
378                 new ArrayList<>();
379         List<AtomsProto.CarWatchdogDailyIoUsageSummary> user100VendorPkgSummaries =
380                 new ArrayList<>();
381         List<AtomsProto.CarWatchdogDailyIoUsageSummary> user101SystemPkgSummaries =
382                 new ArrayList<>();
383         for (int i = 15; i > 7; --i) {
384             user101VendorPkgSummaries.add(CarWatchdogServiceUnitTest
385                     .constructCarWatchdogDailyIoUsageSummary(/* fgWrBytes= */ 4101L * i,
386                             /* bgWrBytes= */ 5101L * i, /* gmWrBytes= */ 6101L * i,
387                             /* overuseCount= */ 1));
388             user100VendorPkgSummaries.add(CarWatchdogServiceUnitTest
389                     .constructCarWatchdogDailyIoUsageSummary(/* fgWrBytes= */ 4100L * i,
390                             /* bgWrBytes= */ 5100L * i, /* gmWrBytes= */ 6100L * i,
391                             /* overuseCount= */ 1));
392             user101SystemPkgSummaries.add(CarWatchdogServiceUnitTest
393                     .constructCarWatchdogDailyIoUsageSummary(/* fgWrBytes= */ 1101L * i,
394                             /* bgWrBytes= */ 2101L * i, /* gmWrBytes= */ 3101L * i,
395                             /* overuseCount= */ 2));
396         }
397         List<WatchdogStorage.UserPackageDailySummaries> expected = Arrays.asList(
398                 new WatchdogStorage.UserPackageDailySummaries(/* userId= */ 101,
399                         /* packageName= */ "vendor_package.critical.C", user101VendorPkgSummaries),
400                 new WatchdogStorage.UserPackageDailySummaries(/* userId= */ 100,
401                         /* packageName= */ "vendor_package.critical.C", user100VendorPkgSummaries),
402                 new WatchdogStorage.UserPackageDailySummaries(/* userId= */ 101,
403                         /* packageName= */ "system_package.non_critical.A",
404                         user101SystemPkgSummaries));
405 
406         assertWithMessage("Top users daily I/O usage summaries").that(actual).isEqualTo(expected);
407     }
408 
409     @Test
testGetTopUsersDailyIoUsageSummariesWithLowSystemTotalWrittenBytes()410     public void testGetTopUsersDailyIoUsageSummariesWithLowSystemTotalWrittenBytes()
411             throws Exception {
412         injectSampleUserPackageSettings();
413         List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
414         for (int i = 1; i <= 30; ++i) {
415             entries.addAll(sampleStatsBetweenDates(/* includingStartDaysAgo= */ i,
416                     /* excludingEndDaysAgo= */ i + 1, /* writtenBytesMultiplier= */ i));
417         }
418 
419         assertWithMessage("Save I/O usage stats").that(mService.saveIoUsageStats(entries)).isTrue();
420 
421         ZonedDateTime currentDate = mTimeSource.getCurrentDate();
422 
423         List<WatchdogStorage.UserPackageDailySummaries> actual =
424                 mService.getTopUsersDailyIoUsageSummaries(/* numTopUsers= */ 3,
425                         /* minSystemTotalWrittenBytes= */ 4_000_000,
426                         /* includingStartEpochSeconds= */ currentDate.minusDays(15).toEpochSecond(),
427                         /* excludingEndEpochSeconds= */ currentDate.minusDays(7).toEpochSecond());
428 
429         assertWithMessage("Top users daily I/O usage summaries").that(actual).isNull();
430     }
431 
432     @Test
testGetTopUsersDailyIoUsageSummariesWithoutStats()433     public void testGetTopUsersDailyIoUsageSummariesWithoutStats() throws Exception {
434         injectSampleUserPackageSettings();
435         List<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
436         for (int i = 1; i <= 7; ++i) {
437             entries.addAll(sampleStatsBetweenDates(/* includingStartDaysAgo= */ i,
438                     /* excludingEndDaysAgo= */ i + 1, /* writtenBytesMultiplier= */ i));
439         }
440 
441         assertWithMessage("Save I/O usage stats").that(mService.saveIoUsageStats(entries)).isTrue();
442 
443         ZonedDateTime currentDate = mTimeSource.getCurrentDate();
444 
445         List<WatchdogStorage.UserPackageDailySummaries> actual =
446                 mService.getTopUsersDailyIoUsageSummaries(/* numTopUsers= */ 3,
447                         /* minSystemTotalWrittenBytes= */ 600_000,
448                         /* includingStartEpochSeconds= */ currentDate.minusDays(15).toEpochSecond(),
449                         /* excludingEndEpochSeconds= */ currentDate.minusDays(7).toEpochSecond());
450 
451         assertWithMessage("Top users daily I/O usage summaries").that(actual).isNull();
452     }
453 
454     @Test
testDeleteUserPackage()455     public void testDeleteUserPackage() throws Exception {
456         ArrayList<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
457         List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = sampleStatsForToday();
458 
459         assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
460         assertThat(mService.saveIoUsageStats(ioUsageStatsEntries)).isTrue();
461 
462         int deleteUserId = 100;
463         String deletePackageName = "system_package.non_critical.A";
464 
465         mService.deleteUserPackage(deleteUserId, deletePackageName);
466 
467         settingsEntries.removeIf(
468                 (s) -> s.userId == deleteUserId && s.packageName.equals(deletePackageName));
469 
470         assertWithMessage("User package settings after deleting a user package")
471                 .that(mService.getUserPackageSettings()).containsExactlyElementsIn(settingsEntries);
472 
473         ioUsageStatsEntries.removeIf(
474                 (e) -> e.userId == deleteUserId && e.packageName.equals(deletePackageName));
475 
476         IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
477                 .containsExactlyElementsIn(ioUsageStatsEntries);
478     }
479 
480     @Test
testDeleteUserPackageWithNonexistentPackage()481     public void testDeleteUserPackageWithNonexistentPackage() throws Exception {
482         injectSampleUserPackageSettings();
483         List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = sampleStatsForToday();
484 
485         assertThat(mService.saveIoUsageStats(ioUsageStatsEntries)).isTrue();
486 
487         int deleteUserId = 100;
488         String deletePackageName = "system_package.non_existent.A";
489 
490         mService.deleteUserPackage(deleteUserId, deletePackageName);
491 
492         assertWithMessage("User package settings").that(mService.getUserPackageSettings())
493                 .containsExactlyElementsIn(sampleSettings());
494 
495         ioUsageStatsEntries.removeIf(
496                 (e) -> e.userId == deleteUserId && e.packageName.equals(deletePackageName));
497 
498         IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
499                 .containsExactlyElementsIn(ioUsageStatsEntries);
500     }
501 
502     @Test
testDeleteUserPackageWithHistoricalIoOveruseStats()503     public void testDeleteUserPackageWithHistoricalIoOveruseStats()
504             throws Exception {
505         ArrayList<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
506 
507         assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
508         assertThat(mService.saveIoUsageStats(sampleStatsBetweenDates(
509                 /* includingStartDaysAgo= */ 1, /* excludingEndDaysAgo= */ 6))).isTrue();
510 
511         int deleteUserId = 100;
512         String deletePackageName = "system_package.non_critical.A";
513 
514         mService.deleteUserPackage(deleteUserId, deletePackageName);
515 
516         settingsEntries.removeIf(
517                 (s) -> s.userId == deleteUserId && s.packageName.equals(deletePackageName));
518 
519         assertWithMessage("User package settings after deleting user package with historical stats")
520                 .that(mService.getUserPackageSettings()).containsExactlyElementsIn(settingsEntries);
521 
522         IoOveruseStats actual = mService.getHistoricalIoOveruseStats(
523                 /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 7);
524 
525         assertWithMessage("Fetched historical I/O overuse stats").that(actual).isNull();
526     }
527 
528     @Test
testSyncUsers()529     public void testSyncUsers() throws Exception {
530         List<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
531         List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = sampleStatsForToday();
532 
533         assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
534         assertThat(mService.saveIoUsageStats(ioUsageStatsEntries)).isTrue();
535 
536         mService.syncUsers(/* aliveUserIds= */ new int[] {101});
537 
538         settingsEntries.removeIf((s) -> s.userId == 100);
539         ioUsageStatsEntries.removeIf((e) -> e.userId == 100);
540 
541         assertWithMessage("User package settings after syncing alive users")
542                 .that(mService.getUserPackageSettings()).containsExactlyElementsIn(settingsEntries);
543 
544         IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
545                 .containsExactlyElementsIn(ioUsageStatsEntries);
546     }
547 
548     @Test
testSyncUsersWithHistoricalIoOveruseStats()549     public void testSyncUsersWithHistoricalIoOveruseStats() throws Exception {
550         List<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
551 
552         assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
553         assertThat(mService.saveIoUsageStats(sampleStatsBetweenDates(
554                 /* includingStartDaysAgo= */ 1, /* excludingEndDaysAgo= */ 6))).isTrue();
555 
556         mService.syncUsers(/* aliveUserIds= */ new int[] {101});
557 
558         settingsEntries.removeIf((s) -> s.userId == 100);
559 
560         assertWithMessage("User package settings after syncing alive users with historical stats")
561                 .that(mService.getUserPackageSettings()).containsExactlyElementsIn(settingsEntries);
562 
563         IoOveruseStats actualSystemPackage = mService.getHistoricalIoOveruseStats(
564                 /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 7);
565         IoOveruseStats actualVendorPackage = mService.getHistoricalIoOveruseStats(
566                 /* userId= */ 100, "vendor_package.critical.C", /* numDaysAgo= */ 7);
567 
568         assertWithMessage("System I/O overuse stats for deleted user")
569                 .that(actualSystemPackage).isNull();
570         assertWithMessage("Vendor I/O overuse stats for deleted user")
571                 .that(actualVendorPackage).isNull();
572     }
573 
574     @Test
testSyncUsersWithNoDataForDeletedUser()575     public void testSyncUsersWithNoDataForDeletedUser() throws Exception {
576         List<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
577         List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = sampleStatsForToday();
578 
579         assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
580         assertThat(mService.saveIoUsageStats(ioUsageStatsEntries)).isTrue();
581 
582         mService.syncUsers(/* aliveUserIds= */ new int[] {100, 101});
583 
584         assertWithMessage("User package settings after syncing users")
585                 .that(mService.getUserPackageSettings()).containsExactlyElementsIn(settingsEntries);
586         IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
587                 .containsExactlyElementsIn(ioUsageStatsEntries);
588     }
589 
590     @Test
testTruncateStatsOutsideRetentionPeriodOnDateChange()591     public void testTruncateStatsOutsideRetentionPeriodOnDateChange() throws Exception {
592         injectSampleUserPackageSettings();
593         mTimeSource.updateNow(/* numDaysAgo= */ 1);
594 
595         assertThat(mService.saveIoUsageStats(sampleStatsBetweenDates(
596                 /* includingStartDaysAgo= */ 0, /* excludingEndDaysAgo= */ 40),
597                 /* shouldCheckRetention= */ false)).isTrue();
598 
599         IoOveruseStats actual  = mService.getHistoricalIoOveruseStats(
600                 /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 40);
601 
602         assertWithMessage("Fetched I/O overuse stats").that(actual).isNotNull();
603 
604         ZonedDateTime currentDate = mTimeSource.getCurrentDate();
605         long startTime = currentDate.minus(39, STATS_TEMPORAL_UNIT).toEpochSecond();
606         long duration = currentDate.toEpochSecond() - startTime;
607         IoOveruseStats expected = new IoOveruseStats.Builder(startTime, duration)
608                 .setTotalOveruses(78).setTotalTimesKilled(39).setTotalBytesWritten(245_700).build();
609 
610         IoOveruseStatsSubject.assertWithMessage(
611                 "Fetched stats only for 39 days. Expected stats (%s) equals actual stats (%s)",
612                 expected.toString(), actual.toString()).that(actual)
613                 .isEqualTo(expected);
614 
615         mTimeSource.updateNow(/* numDaysAgo= */ 0);
616         mService.shrinkDatabase();
617 
618         actual = mService.getHistoricalIoOveruseStats(
619                 /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 40);
620 
621         assertWithMessage("Fetched I/O overuse stats").that(actual).isNotNull();
622 
623         currentDate = mTimeSource.getCurrentDate();
624         startTime = currentDate.minus(RETENTION_PERIOD.minusDays(1)).toEpochSecond();
625         duration = currentDate.toEpochSecond() - startTime;
626         expected = new IoOveruseStats.Builder(startTime, duration)
627                 .setTotalOveruses(58).setTotalTimesKilled(29).setTotalBytesWritten(182_700).build();
628 
629         IoOveruseStatsSubject.assertWithMessage("Fetched stats only within retention period. "
630                         + "Expected stats (%s) equals actual stats (%s)",
631                 expected.toString(), actual.toString()).that(actual).isEqualTo(expected);
632     }
633 
634     @Test
testForgiveHistoricalOveruses()635     public void testForgiveHistoricalOveruses() throws Exception {
636         injectSampleUserPackageSettings();
637 
638         assertThat(mService.saveIoUsageStats(sampleStatsBetweenDates(/* includingStartDaysAgo= */ 1,
639                 /* excludingEndDaysAgo= */ 3))).isTrue();
640 
641         List<WatchdogStorage.NotForgivenOverusesEntry> expectedOveruses = Arrays.asList(
642                 new WatchdogStorage.NotForgivenOverusesEntry(100, "system_package.non_critical.A",
643                         2),
644                 new WatchdogStorage.NotForgivenOverusesEntry(101, "system_package.non_critical.A",
645                         2),
646                 new WatchdogStorage.NotForgivenOverusesEntry(100, "vendor_package.critical.C", 2),
647                 new WatchdogStorage.NotForgivenOverusesEntry(101, "vendor_package.critical.C", 2));
648 
649         assertWithMessage("Not forgiven historical overuses before forgiving")
650                 .that(mService.getNotForgivenHistoricalIoOveruses(/* numDaysAgo= */ 7))
651                 .containsExactlyElementsIn(expectedOveruses);
652 
653         SparseArray<List<String>> packagesToForgiveByUserId = new SparseArray<>();
654         packagesToForgiveByUserId.put(100,
655                 Collections.singletonList("system_package.non_critical.A"));
656         packagesToForgiveByUserId.put(101, Collections.singletonList("vendor_package.critical.C"));
657 
658         mService.forgiveHistoricalOveruses(packagesToForgiveByUserId, /* numDaysAgo= */ 7);
659 
660         expectedOveruses = Arrays.asList(
661                 new WatchdogStorage.NotForgivenOverusesEntry(101, "system_package.non_critical.A",
662                         2),
663                 new WatchdogStorage.NotForgivenOverusesEntry(100, "vendor_package.critical.C", 2));
664 
665         assertWithMessage("Not forgiven historical overuses after forgiving")
666                 .that(mService.getNotForgivenHistoricalIoOveruses(/* numDaysAgo= */ 7))
667                 .containsExactlyElementsIn(expectedOveruses);
668     }
669 
670     @Test
testUserPackageSettingsAfterUpgradeToVersion2()671     public void testUserPackageSettingsAfterUpgradeToVersion2() throws Exception {
672         SQLiteDatabase db = createDatabaseAndUpgradeToVersion2();
673 
674         List<WatchdogStorage.UserPackageSettingsEntry> actual = new ArrayList<>();
675         try (Cursor cursor = db.rawQuery("SELECT user_id, package_name, killable_state FROM "
676                 + WatchdogStorage.UserPackageSettingsTable.TABLE_NAME, null, null)) {
677             while (cursor.moveToNext()) {
678                 actual.add(new WatchdogStorage.UserPackageSettingsEntry(cursor.getInt(0),
679                         cursor.getString(1), cursor.getInt(2)));
680             }
681         }
682 
683         List<WatchdogStorage.UserPackageSettingsEntry> expected =
684                 Arrays.asList(new WatchdogStorage.UserPackageSettingsEntry(100, "package_A", 1),
685                         new WatchdogStorage.UserPackageSettingsEntry(101, "package_B", 2));
686 
687         assertWithMessage("User package settings").that(actual).containsExactlyElementsIn(expected);
688     }
689 
690     @Test
testTablesAfterUpgradeToVersion2()691     public void testTablesAfterUpgradeToVersion2() throws Exception {
692         SQLiteDatabase db = createDatabaseAndUpgradeToVersion2();
693 
694         List<String> actual = new ArrayList<>();
695         try (Cursor cursor = db.query(/* table= */ "sqlite_master",
696                 /* columns= */ new String[]{"name"},
697                 /* selection= */ "name != ? and name not like ?",
698                 /* selectionArgs= */ new String[]{"android_metadata", "sqlite_%"},
699                 /* groupBy= */ null, /* having= */ null, /* orderBy= */null)) {
700             while (cursor.moveToNext()) {
701                 actual.add(cursor.getString(0));
702             }
703         }
704 
705         assertWithMessage("Table names").that(actual).containsExactlyElementsIn(
706                 Arrays.asList(WatchdogStorage.UserPackageSettingsTable.TABLE_NAME,
707                         WatchdogStorage.IoUsageStatsTable.TABLE_NAME));
708     }
709 
injectSampleUserPackageSettings()710     private void injectSampleUserPackageSettings() throws Exception {
711         List<WatchdogStorage.UserPackageSettingsEntry> userPackageSettingEntries = sampleSettings();
712 
713         assertThat(mService.saveUserPackageSettings(userPackageSettingEntries)).isTrue();
714     }
715 
sampleSettings()716     private static ArrayList<WatchdogStorage.UserPackageSettingsEntry> sampleSettings() {
717         return new ArrayList<>(Arrays.asList(
718                 new WatchdogStorage.UserPackageSettingsEntry(
719                         /* userId= */ 100, "system_package.non_critical.A", KILLABLE_STATE_YES),
720                 new WatchdogStorage.UserPackageSettingsEntry(
721                         /* userId= */ 100, "system_package.non_critical.B", KILLABLE_STATE_NO),
722                 new WatchdogStorage.UserPackageSettingsEntry(
723                         /* userId= */ 100, "vendor_package.critical.C", KILLABLE_STATE_NEVER),
724                 new WatchdogStorage.UserPackageSettingsEntry(
725                         /* userId= */ 101, "system_package.non_critical.A", KILLABLE_STATE_NO),
726                 new WatchdogStorage.UserPackageSettingsEntry(
727                         /* userId= */ 101, "system_package.non_critical.B", KILLABLE_STATE_YES),
728                 new WatchdogStorage.UserPackageSettingsEntry(
729                         /* userId= */ 101, "vendor_package.critical.C", KILLABLE_STATE_NEVER)));
730     }
731 
sampleStatsBetweenDates( int includingStartDaysAgo, int excludingEndDaysAgo)732     private ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsBetweenDates(
733             int includingStartDaysAgo, int excludingEndDaysAgo) {
734         return sampleStatsBetweenDates(includingStartDaysAgo, excludingEndDaysAgo,
735                 /* writtenBytesMultiplier= */ 1);
736     }
737 
sampleStatsBetweenDates( int includingStartDaysAgo, int excludingEndDaysAgo, int writtenBytesMultiplier)738     private ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsBetweenDates(
739             int includingStartDaysAgo, int excludingEndDaysAgo, int writtenBytesMultiplier) {
740         ZonedDateTime currentDate = mTimeSource.getCurrentDate();
741         ArrayList<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
742         for (int i = includingStartDaysAgo; i < excludingEndDaysAgo; ++i) {
743             entries.addAll(
744                     sampleStatsForDate(currentDate.minus(i, STATS_TEMPORAL_UNIT).toEpochSecond(),
745                             STATS_TEMPORAL_UNIT.getDuration().toSeconds(), writtenBytesMultiplier));
746         }
747         return entries;
748     }
749 
sampleStatsForToday()750     private ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsForToday() {
751         long statsDateEpoch = mTimeSource.getCurrentDate().toEpochSecond();
752         long duration = mTimeSource.getCurrentDateTime().toEpochSecond() - statsDateEpoch;
753         return sampleStatsForDate(statsDateEpoch, duration, /* writtenBytesMultiplier= */ 1);
754     }
755 
sampleStatsForDate( long statsDateEpoch, long duration)756     private static ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsForDate(
757             long statsDateEpoch, long duration) {
758         return sampleStatsForDate(statsDateEpoch, duration, /* writtenBytesMultiplier= */ 1);
759     }
760 
sampleStatsForDate( long statsDateEpoch, long duration, int writtenBytesMultiplier)761     private static ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsForDate(
762             long statsDateEpoch, long duration, int writtenBytesMultiplier) {
763         ArrayList<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
764         for (int i = 100; i <= 101; ++i) {
765             entries.add(constructIoUsageStatsEntry(
766                     /* userId= */ i, "system_package.non_critical.A", statsDateEpoch, duration,
767                     /* remainingWriteBytes= */
768                     CarWatchdogServiceUnitTest.constructPerStateBytes(200L, 300L, 400L),
769                     /* writtenBytes= */
770                     CarWatchdogServiceUnitTest.constructPerStateBytes(
771                             (1000L + i) * writtenBytesMultiplier,
772                             (2000L + i) * writtenBytesMultiplier,
773                             (3000L + i) * writtenBytesMultiplier),
774                     /* forgivenWriteBytes= */
775                     CarWatchdogServiceUnitTest.constructPerStateBytes(100L, 100L, 100L),
776                     /* totalOveruses= */ 2, /* forgivenOveruses= */ 1, /* totalTimesKilled= */ 1));
777             entries.add(constructIoUsageStatsEntry(
778                     /* userId= */ i, "vendor_package.critical.C", statsDateEpoch, duration,
779                     /* remainingWriteBytes= */
780                     CarWatchdogServiceUnitTest.constructPerStateBytes(500L, 600L, 700L),
781                     /* writtenBytes= */
782                     CarWatchdogServiceUnitTest.constructPerStateBytes(
783                             (4000L + i) * writtenBytesMultiplier,
784                             (5000L + i) * writtenBytesMultiplier,
785                             (6000L + i) * writtenBytesMultiplier),
786                     /* forgivenWriteBytes= */
787                     CarWatchdogServiceUnitTest.constructPerStateBytes(200L, 200L, 200L),
788                     /* totalOveruses= */ 1, /* forgivenOveruses= */ 0, /* totalTimesKilled= */ 0));
789         }
790         return entries;
791     }
792 
constructIoUsageStatsEntry( int userId, String packageName, long startTime, long duration, PerStateBytes remainingWriteBytes, PerStateBytes writtenBytes, PerStateBytes forgivenWriteBytes, int totalOveruses, int forgivenOveruses, int totalTimesKilled)793     static WatchdogStorage.IoUsageStatsEntry constructIoUsageStatsEntry(
794             int userId, String packageName, long startTime, long duration,
795             PerStateBytes remainingWriteBytes, PerStateBytes writtenBytes,
796             PerStateBytes forgivenWriteBytes, int totalOveruses, int forgivenOveruses,
797             int totalTimesKilled) {
798         WatchdogPerfHandler.PackageIoUsage ioUsage = new WatchdogPerfHandler.PackageIoUsage(
799                 constructInternalIoOveruseStats(startTime, duration, remainingWriteBytes,
800                         writtenBytes, totalOveruses), forgivenWriteBytes, forgivenOveruses,
801                 totalTimesKilled);
802         return new WatchdogStorage.IoUsageStatsEntry(userId, packageName, ioUsage);
803     }
804 
constructInternalIoOveruseStats( long startTime, long duration, PerStateBytes remainingWriteBytes, PerStateBytes writtenBytes, int totalOveruses)805     private static android.automotive.watchdog.IoOveruseStats constructInternalIoOveruseStats(
806             long startTime, long duration, PerStateBytes remainingWriteBytes,
807             PerStateBytes writtenBytes, int totalOveruses) {
808         android.automotive.watchdog.IoOveruseStats stats =
809                 new android.automotive.watchdog.IoOveruseStats();
810         stats.startTime = startTime;
811         stats.durationInSeconds = duration;
812         stats.remainingWriteBytes = remainingWriteBytes;
813         stats.writtenBytes = writtenBytes;
814         stats.totalOveruses = totalOveruses;
815         return stats;
816     }
817 
createDatabaseAndUpgradeToVersion2()818     private SQLiteDatabase createDatabaseAndUpgradeToVersion2() {
819         SQLiteDatabase db = SQLiteDatabase.create(null);
820         assertWithMessage("Create database version 1").that(DatabaseVersion1.create(db)).isTrue();
821 
822         WatchdogStorage.WatchdogDbHelper dbHelper =
823                 new WatchdogStorage.WatchdogDbHelper(mContext, /* useDataSystemCarDir= */ false,
824                         mTimeSource);
825         dbHelper.onUpgrade(db, /*oldVersion=*/ 1, /*newVersion=*/ 2);
826 
827         return db;
828     }
829 
830     private static final class TestTimeSource extends TimeSource {
831         private static final Instant TEST_DATE_TIME = Instant.parse("2021-11-12T13:14:15.16Z");
832         private Instant mNow;
TestTimeSource()833         TestTimeSource() {
834             mNow = TEST_DATE_TIME;
835         }
836 
837         @Override
now()838         public Instant now() {
839             /* Return the same time, so the tests are deterministic. */
840             return mNow;
841         }
842 
843         @Override
toString()844         public String toString() {
845             return "Mocked date to " + now();
846         }
847 
updateNow(int numDaysAgo)848         void updateNow(int numDaysAgo) {
849             mNow = TEST_DATE_TIME.minus(numDaysAgo, ChronoUnit.DAYS);
850         }
851     };
852 
853     private static final class DatabaseVersion1 {
854         private static final String CREATE_TABLE_SQL =
855                 "CREATE TABLE user_package_settings (user_id INTEGER NOT NULL, "
856                 + "package_name TEXT NOT NULL, killable_state INTEGER NOT NULL, PRIMARY KEY "
857                 + "(package_name, user_id))";
858 
859         private static final String[] INSERT_SQLS = new String[] {
860                 "INSERT INTO user_package_settings (user_id, package_name, killable_state) "
861                 + "VALUES (100, \"package_A\", 1)",
862                 "INSERT INTO user_package_settings (user_id, package_name, killable_state) "
863                 + "VALUES (101, \"package_B\", 2)"};
864 
create(SQLiteDatabase db)865         public static boolean create(SQLiteDatabase db) {
866             boolean isSuccessful = false;
867             db.beginTransaction();
868             try {
869                 db.execSQL(CREATE_TABLE_SQL);
870                 for (String insertSql : INSERT_SQLS) {
871                     db.execSQL(insertSql);
872                 }
873                 db.setTransactionSuccessful();
874                 isSuccessful = true;
875             } finally {
876                 db.endTransaction();
877             }
878             return isSuccessful;
879         }
880     }
881 }
882