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.server.tare; 18 19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; 20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 21 import static com.android.server.tare.TareTestUtils.assertLedgersEqual; 22 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertNotNull; 25 import static org.junit.Assert.assertNull; 26 import static org.mockito.Mockito.when; 27 28 import android.app.tare.EconomyManager; 29 import android.content.Context; 30 import android.content.pm.ApplicationInfo; 31 import android.content.pm.PackageInfo; 32 import android.os.UserHandle; 33 import android.util.Log; 34 import android.util.SparseArrayMap; 35 36 import androidx.test.InstrumentationRegistry; 37 import androidx.test.filters.SmallTest; 38 import androidx.test.runner.AndroidJUnit4; 39 40 import com.android.server.LocalServices; 41 42 import org.junit.After; 43 import org.junit.Before; 44 import org.junit.Test; 45 import org.junit.runner.RunWith; 46 import org.mockito.ArgumentCaptor; 47 import org.mockito.InOrder; 48 import org.mockito.Mock; 49 import org.mockito.MockitoSession; 50 import org.mockito.quality.Strictness; 51 52 import java.io.File; 53 import java.util.ArrayList; 54 import java.util.List; 55 56 /** 57 * Tests for various Scribe behavior, including reading and writing correctly from file. 58 * 59 * atest FrameworksServicesTests:ScribeTest 60 */ 61 @RunWith(AndroidJUnit4.class) 62 @SmallTest 63 public class ScribeTest { 64 private static final String TAG = "ScribeTest"; 65 66 private static final int TEST_USER_ID = 27; 67 private static final String TEST_PACKAGE = "com.android.test"; 68 69 private MockitoSession mMockingSession; 70 private Scribe mScribeUnderTest; 71 private File mTestFileDir; 72 private final SparseArrayMap<String, InstalledPackageInfo> mInstalledPackages = 73 new SparseArrayMap<>(); 74 private final List<Analyst.Report> mReports = new ArrayList<>(); 75 76 @Mock 77 private Analyst mAnalyst; 78 @Mock 79 private InternalResourceService mIrs; 80 getContext()81 private Context getContext() { 82 return InstrumentationRegistry.getContext(); 83 } 84 85 @Before setUp()86 public void setUp() throws Exception { 87 mMockingSession = mockitoSession() 88 .initMocks(this) 89 .strictness(Strictness.LENIENT) 90 .mockStatic(LocalServices.class) 91 .startMocking(); 92 when(mIrs.getLock()).thenReturn(new Object()); 93 when(mIrs.getEnabledMode()).thenReturn(EconomyManager.ENABLED_MODE_ON); 94 when(mIrs.getInstalledPackages()).thenReturn(mInstalledPackages); 95 when(mAnalyst.getReports()).thenReturn(mReports); 96 mTestFileDir = new File(getContext().getFilesDir(), "scribe_test"); 97 //noinspection ResultOfMethodCallIgnored 98 mTestFileDir.mkdirs(); 99 Log.d(TAG, "Saving data to '" + mTestFileDir + "'"); 100 mScribeUnderTest = new Scribe(mIrs, mAnalyst, mTestFileDir); 101 102 addInstalledPackage(TEST_USER_ID, TEST_PACKAGE); 103 } 104 105 @After tearDown()106 public void tearDown() throws Exception { 107 mScribeUnderTest.tearDownLocked(); 108 if (mTestFileDir.exists() && !mTestFileDir.delete()) { 109 Log.w(TAG, "Failed to delete test file directory"); 110 } 111 if (mMockingSession != null) { 112 mMockingSession.finishMocking(); 113 } 114 } 115 116 @Test testWritingAnalystReportsToDisk()117 public void testWritingAnalystReportsToDisk() { 118 ArgumentCaptor<List<Analyst.Report>> reportCaptor = 119 ArgumentCaptor.forClass(List.class); 120 121 InOrder inOrder = inOrder(mAnalyst); 122 123 // Empty set 124 mReports.clear(); 125 mScribeUnderTest.writeImmediatelyForTesting(); 126 mScribeUnderTest.loadFromDiskLocked(); 127 inOrder.verify(mAnalyst).loadReports(reportCaptor.capture()); 128 List<Analyst.Report> result = reportCaptor.getValue(); 129 assertReportListsEqual(mReports, result); 130 131 Analyst.Report report1 = new Analyst.Report(); 132 report1.cumulativeBatteryDischarge = 1; 133 report1.currentBatteryLevel = 2; 134 report1.cumulativeProfit = 3; 135 report1.numProfitableActions = 4; 136 report1.cumulativeLoss = 5; 137 report1.numUnprofitableActions = 6; 138 report1.cumulativeRewards = 7; 139 report1.numRewards = 8; 140 report1.cumulativePositiveRegulations = 9; 141 report1.numPositiveRegulations = 10; 142 report1.cumulativeNegativeRegulations = 11; 143 report1.numNegativeRegulations = 12; 144 report1.screenOffDurationMs = 13; 145 report1.screenOffDischargeMah = 14; 146 mReports.add(report1); 147 mScribeUnderTest.writeImmediatelyForTesting(); 148 mScribeUnderTest.loadFromDiskLocked(); 149 inOrder.verify(mAnalyst).loadReports(reportCaptor.capture()); 150 result = reportCaptor.getValue(); 151 assertReportListsEqual(mReports, result); 152 153 Analyst.Report report2 = new Analyst.Report(); 154 report2.cumulativeBatteryDischarge = 10; 155 report2.currentBatteryLevel = 20; 156 report2.cumulativeProfit = 30; 157 report2.numProfitableActions = 40; 158 report2.cumulativeLoss = 50; 159 report2.numUnprofitableActions = 60; 160 report2.cumulativeRewards = 70; 161 report2.numRewards = 80; 162 report2.cumulativePositiveRegulations = 90; 163 report2.numPositiveRegulations = 100; 164 report2.cumulativeNegativeRegulations = 110; 165 report2.numNegativeRegulations = 120; 166 report2.screenOffDurationMs = 130; 167 report2.screenOffDischargeMah = 140; 168 mReports.add(report2); 169 mScribeUnderTest.writeImmediatelyForTesting(); 170 mScribeUnderTest.loadFromDiskLocked(); 171 inOrder.verify(mAnalyst).loadReports(reportCaptor.capture()); 172 result = reportCaptor.getValue(); 173 assertReportListsEqual(mReports, result); 174 } 175 176 @Test testWriteHighLevelStateToDisk()177 public void testWriteHighLevelStateToDisk() { 178 long lastReclamationTime = System.currentTimeMillis(); 179 long remainingConsumableCakes = 2000L; 180 long consumptionLimit = 500_000L; 181 when(mIrs.getConsumptionLimitLocked()).thenReturn(consumptionLimit); 182 183 Ledger ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE); 184 ledger.recordTransaction( 185 new Ledger.Transaction(0, 1000L, EconomicPolicy.TYPE_REWARD | 1, null, 2000, 0)); 186 // Negative ledger balance shouldn't affect the total circulation value. 187 ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID + 1, TEST_PACKAGE); 188 ledger.recordTransaction( 189 new Ledger.Transaction(0, 1000L, 190 EconomicPolicy.TYPE_ACTION | 1, null, -5000, 3000)); 191 mScribeUnderTest.setLastReclamationTimeLocked(lastReclamationTime); 192 mScribeUnderTest.setConsumptionLimitLocked(consumptionLimit); 193 mScribeUnderTest.adjustRemainingConsumableCakesLocked( 194 remainingConsumableCakes - consumptionLimit); 195 196 assertEquals(lastReclamationTime, mScribeUnderTest.getLastReclamationTimeLocked()); 197 assertEquals(remainingConsumableCakes, 198 mScribeUnderTest.getRemainingConsumableCakesLocked()); 199 assertEquals(consumptionLimit, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); 200 201 mScribeUnderTest.writeImmediatelyForTesting(); 202 mScribeUnderTest.loadFromDiskLocked(); 203 204 assertEquals(lastReclamationTime, mScribeUnderTest.getLastReclamationTimeLocked()); 205 assertEquals(remainingConsumableCakes, 206 mScribeUnderTest.getRemainingConsumableCakesLocked()); 207 assertEquals(consumptionLimit, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); 208 } 209 210 @Test testWritingEmptyLedgerToDisk()211 public void testWritingEmptyLedgerToDisk() { 212 final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE); 213 mScribeUnderTest.writeImmediatelyForTesting(); 214 215 mScribeUnderTest.loadFromDiskLocked(); 216 assertLedgersEqual(ogLedger, mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE)); 217 } 218 219 @Test testWritingPopulatedLedgerToDisk()220 public void testWritingPopulatedLedgerToDisk() { 221 final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE); 222 ogLedger.recordTransaction( 223 new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD | 1, null, 51, 0)); 224 ogLedger.recordTransaction( 225 new Ledger.Transaction(1500, 2000, 226 EconomicPolicy.TYPE_REWARD | 2, "green", 52, -1)); 227 ogLedger.recordTransaction( 228 new Ledger.Transaction(2500, 3000, EconomicPolicy.TYPE_REWARD | 3, "blue", 3, 12)); 229 mScribeUnderTest.writeImmediatelyForTesting(); 230 231 mScribeUnderTest.loadFromDiskLocked(); 232 assertLedgersEqual(ogLedger, mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE)); 233 } 234 235 @Test testWritingMultipleLedgersToDisk()236 public void testWritingMultipleLedgersToDisk() { 237 final SparseArrayMap<String, Ledger> ledgers = new SparseArrayMap<>(); 238 final int numUsers = 3; 239 final int numLedgers = 5; 240 for (int u = 0; u < numUsers; ++u) { 241 final int userId = TEST_USER_ID + u; 242 for (int l = 0; l < numLedgers; ++l) { 243 final String pkgName = TEST_PACKAGE + l; 244 addInstalledPackage(userId, pkgName); 245 final Ledger ledger = mScribeUnderTest.getLedgerLocked(userId, pkgName); 246 ledger.recordTransaction(new Ledger.Transaction( 247 0, 1000L * u + l, EconomicPolicy.TYPE_ACTION | 1, null, -51L * u + l, 50)); 248 ledger.recordTransaction(new Ledger.Transaction( 249 1500L * u + l, 2000L * u + l, 250 EconomicPolicy.TYPE_REWARD | 2 * u + l, "green" + u + l, 52L * u + l, 0)); 251 ledger.recordTransaction(new Ledger.Transaction( 252 2500L * u + l, 3000L * u + l, 253 EconomicPolicy.TYPE_REWARD | 3 * u + l, "blue" + u + l, 3L * u + l, 0)); 254 ledgers.add(userId, pkgName, ledger); 255 } 256 } 257 mScribeUnderTest.writeImmediatelyForTesting(); 258 259 mScribeUnderTest.loadFromDiskLocked(); 260 ledgers.forEach((userId, pkgName, ledger) 261 -> assertLedgersEqual(ledger, mScribeUnderTest.getLedgerLocked(userId, pkgName))); 262 } 263 264 @Test testDiscardLedgerFromDisk()265 public void testDiscardLedgerFromDisk() { 266 final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE); 267 ogLedger.recordTransaction( 268 new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD | 1, null, 51, 1)); 269 ogLedger.recordTransaction( 270 new Ledger.Transaction(1500, 2000, EconomicPolicy.TYPE_REWARD | 2, "green", 52, 0)); 271 ogLedger.recordTransaction( 272 new Ledger.Transaction(2500, 3000, EconomicPolicy.TYPE_REWARD | 3, "blue", 3, 1)); 273 mScribeUnderTest.writeImmediatelyForTesting(); 274 275 mScribeUnderTest.loadFromDiskLocked(); 276 assertLedgersEqual(ogLedger, mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE)); 277 278 mScribeUnderTest.discardLedgerLocked(TEST_USER_ID, TEST_PACKAGE); 279 mScribeUnderTest.writeImmediatelyForTesting(); 280 281 // Make sure there's no more saved ledger. 282 mScribeUnderTest.loadFromDiskLocked(); 283 assertLedgersEqual(new Ledger(), 284 mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE)); 285 } 286 287 @Test testLoadingMissingPackageFromDisk()288 public void testLoadingMissingPackageFromDisk() { 289 final String pkgName = TEST_PACKAGE + ".uninstalled"; 290 final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, pkgName); 291 ogLedger.recordTransaction( 292 new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REGULATION | 1, null, 51, 1)); 293 ogLedger.recordTransaction( 294 new Ledger.Transaction(1500, 2000, EconomicPolicy.TYPE_REWARD | 2, "green", 52, 2)); 295 ogLedger.recordTransaction( 296 new Ledger.Transaction(2500, 3000, EconomicPolicy.TYPE_ACTION | 3, "blue", -3, 3)); 297 mScribeUnderTest.writeImmediatelyForTesting(); 298 299 // Package isn't installed, so make sure it's not saved to memory after loading. 300 mScribeUnderTest.loadFromDiskLocked(); 301 assertLedgersEqual(new Ledger(), mScribeUnderTest.getLedgerLocked(TEST_USER_ID, pkgName)); 302 } 303 304 @Test testLoadingMissingUserFromDisk()305 public void testLoadingMissingUserFromDisk() { 306 final int userId = TEST_USER_ID + 1; 307 final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE); 308 ogLedger.recordTransaction( 309 new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD | 1, null, 51, 0)); 310 ogLedger.recordTransaction( 311 new Ledger.Transaction(1500, 2000, EconomicPolicy.TYPE_REWARD | 2, "green", 52, 1)); 312 ogLedger.recordTransaction( 313 new Ledger.Transaction(2500, 3000, 314 EconomicPolicy.TYPE_REGULATION | 3, "blue", 3, 3)); 315 mScribeUnderTest.writeImmediatelyForTesting(); 316 317 // User doesn't show up with any packages, so make sure nothing is saved after loading. 318 mScribeUnderTest.loadFromDiskLocked(); 319 assertLedgersEqual(new Ledger(), mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE)); 320 } 321 322 @Test testChangingConsumable()323 public void testChangingConsumable() { 324 assertEquals(0, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); 325 assertEquals(0, mScribeUnderTest.getRemainingConsumableCakesLocked()); 326 327 // Limit increased, so remaining value should be adjusted as well 328 mScribeUnderTest.setConsumptionLimitLocked(1000); 329 assertEquals(1000, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); 330 assertEquals(1000, mScribeUnderTest.getRemainingConsumableCakesLocked()); 331 332 // Limit decreased below remaining, so remaining value should be adjusted as well 333 mScribeUnderTest.setConsumptionLimitLocked(500); 334 assertEquals(500, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); 335 assertEquals(500, mScribeUnderTest.getRemainingConsumableCakesLocked()); 336 337 mScribeUnderTest.adjustRemainingConsumableCakesLocked(-100); 338 assertEquals(500, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); 339 assertEquals(400, mScribeUnderTest.getRemainingConsumableCakesLocked()); 340 341 // Limit increased, so remaining value should be adjusted by the difference as well 342 mScribeUnderTest.setConsumptionLimitLocked(1000); 343 assertEquals(1000, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); 344 assertEquals(900, mScribeUnderTest.getRemainingConsumableCakesLocked()); 345 346 347 // Limit decreased, but above remaining, so remaining value should left alone 348 mScribeUnderTest.setConsumptionLimitLocked(950); 349 assertEquals(950, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); 350 assertEquals(900, mScribeUnderTest.getRemainingConsumableCakesLocked()); 351 } 352 assertReportListsEqual(List<Analyst.Report> expected, List<Analyst.Report> actual)353 private void assertReportListsEqual(List<Analyst.Report> expected, 354 List<Analyst.Report> actual) { 355 if (expected == null) { 356 assertNull(actual); 357 return; 358 } 359 assertNotNull(actual); 360 assertEquals(expected.size(), actual.size()); 361 for (int i = 0; i < expected.size(); ++i) { 362 Analyst.Report eReport = expected.get(i); 363 Analyst.Report aReport = actual.get(i); 364 if (eReport == null) { 365 assertNull(aReport); 366 continue; 367 } 368 assertNotNull(aReport); 369 assertEquals("Reports #" + i + " cumulativeBatteryDischarge are not equal", 370 eReport.cumulativeBatteryDischarge, aReport.cumulativeBatteryDischarge); 371 assertEquals("Reports #" + i + " currentBatteryLevel are not equal", 372 eReport.currentBatteryLevel, aReport.currentBatteryLevel); 373 assertEquals("Reports #" + i + " cumulativeProfit are not equal", 374 eReport.cumulativeProfit, aReport.cumulativeProfit); 375 assertEquals("Reports #" + i + " numProfitableActions are not equal", 376 eReport.numProfitableActions, aReport.numProfitableActions); 377 assertEquals("Reports #" + i + " cumulativeLoss are not equal", 378 eReport.cumulativeLoss, aReport.cumulativeLoss); 379 assertEquals("Reports #" + i + " numUnprofitableActions are not equal", 380 eReport.numUnprofitableActions, aReport.numUnprofitableActions); 381 assertEquals("Reports #" + i + " cumulativeRewards are not equal", 382 eReport.cumulativeRewards, aReport.cumulativeRewards); 383 assertEquals("Reports #" + i + " numRewards are not equal", 384 eReport.numRewards, aReport.numRewards); 385 assertEquals("Reports #" + i + " cumulativePositiveRegulations are not equal", 386 eReport.cumulativePositiveRegulations, aReport.cumulativePositiveRegulations); 387 assertEquals("Reports #" + i + " numPositiveRegulations are not equal", 388 eReport.numPositiveRegulations, aReport.numPositiveRegulations); 389 assertEquals("Reports #" + i + " cumulativeNegativeRegulations are not equal", 390 eReport.cumulativeNegativeRegulations, aReport.cumulativeNegativeRegulations); 391 assertEquals("Reports #" + i + " numNegativeRegulations are not equal", 392 eReport.numNegativeRegulations, aReport.numNegativeRegulations); 393 assertEquals("Reports #" + i + " screenOffDurationMs are not equal", 394 eReport.screenOffDurationMs, aReport.screenOffDurationMs); 395 assertEquals("Reports #" + i + " screenOffDischargeMah are not equal", 396 eReport.screenOffDischargeMah, aReport.screenOffDischargeMah); 397 } 398 } 399 addInstalledPackage(int userId, String pkgName)400 private void addInstalledPackage(int userId, String pkgName) { 401 PackageInfo pkgInfo = new PackageInfo(); 402 pkgInfo.packageName = pkgName; 403 ApplicationInfo applicationInfo = new ApplicationInfo(); 404 applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode())); 405 pkgInfo.applicationInfo = applicationInfo; 406 mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), userId, 407 pkgInfo)); 408 } 409 } 410