Merge "Track basic TARE statistics." into tm-dev
This commit is contained in:
@@ -73,6 +73,7 @@ class Agent {
|
||||
|
||||
private final Object mLock;
|
||||
private final Handler mHandler;
|
||||
private final Analyst mAnalyst;
|
||||
private final InternalResourceService mIrs;
|
||||
private final Scribe mScribe;
|
||||
|
||||
@@ -110,10 +111,11 @@ class Agent {
|
||||
*/
|
||||
private static final int MSG_CHECK_INDIVIDUAL_AFFORDABILITY = 1;
|
||||
|
||||
Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe) {
|
||||
Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe, @NonNull Analyst analyst) {
|
||||
mLock = irs.getLock();
|
||||
mIrs = irs;
|
||||
mScribe = scribe;
|
||||
mAnalyst = analyst;
|
||||
mHandler = new AgentHandler(TareHandlerThread.get().getLooper());
|
||||
mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
|
||||
mBalanceThresholdAlarmQueue = new BalanceThresholdAlarmQueue(
|
||||
@@ -443,7 +445,7 @@ class Agent {
|
||||
void recordTransactionLocked(final int userId, @NonNull final String pkgName,
|
||||
@NonNull Ledger ledger, @NonNull Ledger.Transaction transaction,
|
||||
final boolean notifyOnAffordabilityChange) {
|
||||
if (transaction.delta == 0) {
|
||||
if (!DEBUG && transaction.delta == 0) {
|
||||
// Skip recording transactions with a delta of 0 to save on space.
|
||||
return;
|
||||
}
|
||||
@@ -471,6 +473,7 @@ class Agent {
|
||||
}
|
||||
ledger.recordTransaction(transaction);
|
||||
mScribe.adjustRemainingConsumableCakesLocked(-transaction.ctp);
|
||||
mAnalyst.noteTransaction(transaction);
|
||||
if (transaction.delta != 0 && notifyOnAffordabilityChange) {
|
||||
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
|
||||
mActionAffordabilityNotes.get(userId, pkgName);
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.tare;
|
||||
|
||||
import static com.android.server.tare.EconomicPolicy.TYPE_ACTION;
|
||||
import static com.android.server.tare.EconomicPolicy.TYPE_REGULATION;
|
||||
import static com.android.server.tare.EconomicPolicy.TYPE_REWARD;
|
||||
import static com.android.server.tare.EconomicPolicy.getEventType;
|
||||
import static com.android.server.tare.TareUtils.cakeToString;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Responsible for maintaining statistics and analysis of TARE's performance.
|
||||
*/
|
||||
public class Analyst {
|
||||
private static final String TAG = "TARE-" + Analyst.class.getSimpleName();
|
||||
private static final boolean DEBUG = InternalResourceService.DEBUG
|
||||
|| Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private static final int NUM_PERIODS_TO_RETAIN = 8;
|
||||
|
||||
static final class Report {
|
||||
/** How much the battery was discharged over the tracked period. */
|
||||
public int cumulativeBatteryDischarge = 0;
|
||||
public int currentBatteryLevel = 0;
|
||||
/**
|
||||
* Profit from performing actions. This excludes special circumstances where we charge the
|
||||
* app
|
||||
* less than the action's CTP.
|
||||
*/
|
||||
public long cumulativeProfit = 0;
|
||||
public int numProfitableActions = 0;
|
||||
/**
|
||||
* Losses from performing actions for special circumstances (eg. for a TOP app) where we
|
||||
* charge
|
||||
* the app less than the action's CTP.
|
||||
*/
|
||||
public long cumulativeLoss = 0;
|
||||
public int numUnprofitableActions = 0;
|
||||
/**
|
||||
* The total number of rewards given to apps over this period.
|
||||
*/
|
||||
public long cumulativeRewards = 0;
|
||||
public int numRewards = 0;
|
||||
/**
|
||||
* Regulations that increased an app's balance.
|
||||
*/
|
||||
public long cumulativePositiveRegulations = 0;
|
||||
public int numPositiveRegulations = 0;
|
||||
/**
|
||||
* Regulations that decreased an app's balance.
|
||||
*/
|
||||
public long cumulativeNegativeRegulations = 0;
|
||||
public int numNegativeRegulations = 0;
|
||||
|
||||
private void clear() {
|
||||
cumulativeBatteryDischarge = 0;
|
||||
currentBatteryLevel = 0;
|
||||
cumulativeProfit = 0;
|
||||
numProfitableActions = 0;
|
||||
cumulativeLoss = 0;
|
||||
numUnprofitableActions = 0;
|
||||
cumulativeRewards = 0;
|
||||
numRewards = 0;
|
||||
cumulativePositiveRegulations = 0;
|
||||
numPositiveRegulations = 0;
|
||||
cumulativeNegativeRegulations = 0;
|
||||
numNegativeRegulations = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private int mPeriodIndex = 0;
|
||||
/** How much the battery was discharged over the tracked period. */
|
||||
private final Report[] mReports = new Report[NUM_PERIODS_TO_RETAIN];
|
||||
|
||||
/** Returns the list of most recent reports, with the oldest report first. */
|
||||
@NonNull
|
||||
List<Report> getReports() {
|
||||
final List<Report> list = new ArrayList<>(NUM_PERIODS_TO_RETAIN);
|
||||
for (int i = 1; i <= NUM_PERIODS_TO_RETAIN; ++i) {
|
||||
final int idx = (mPeriodIndex + i) % NUM_PERIODS_TO_RETAIN;
|
||||
final Report report = mReports[idx];
|
||||
if (report != null) {
|
||||
list.add(report);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the given reports instead of whatever is currently saved. Reports should be ordered
|
||||
* oldest to most recent.
|
||||
*/
|
||||
void loadReports(@NonNull List<Report> reports) {
|
||||
final int numReports = reports.size();
|
||||
mPeriodIndex = Math.max(0, numReports - 1);
|
||||
for (int i = 0; i < NUM_PERIODS_TO_RETAIN; ++i) {
|
||||
if (i < numReports) {
|
||||
mReports[i] = reports.get(i);
|
||||
} else {
|
||||
mReports[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void noteBatteryLevelChange(int newBatteryLevel) {
|
||||
if (newBatteryLevel == 100 && mReports[mPeriodIndex] != null
|
||||
&& mReports[mPeriodIndex].currentBatteryLevel < newBatteryLevel) {
|
||||
mPeriodIndex = (mPeriodIndex + 1) % NUM_PERIODS_TO_RETAIN;
|
||||
if (mReports[mPeriodIndex] != null) {
|
||||
final Report report = mReports[mPeriodIndex];
|
||||
report.clear();
|
||||
report.currentBatteryLevel = newBatteryLevel;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mReports[mPeriodIndex] == null) {
|
||||
Report report = new Report();
|
||||
mReports[mPeriodIndex] = report;
|
||||
report.currentBatteryLevel = newBatteryLevel;
|
||||
return;
|
||||
}
|
||||
|
||||
final Report report = mReports[mPeriodIndex];
|
||||
if (newBatteryLevel < report.currentBatteryLevel) {
|
||||
report.cumulativeBatteryDischarge += (report.currentBatteryLevel - newBatteryLevel);
|
||||
}
|
||||
report.currentBatteryLevel = newBatteryLevel;
|
||||
}
|
||||
|
||||
void noteTransaction(@NonNull Ledger.Transaction transaction) {
|
||||
if (mReports[mPeriodIndex] == null) {
|
||||
mReports[mPeriodIndex] = new Report();
|
||||
}
|
||||
final Report report = mReports[mPeriodIndex];
|
||||
switch (getEventType(transaction.eventId)) {
|
||||
case TYPE_ACTION:
|
||||
// For now, assume all instances where price < CTP is a special instance.
|
||||
// TODO: add an explicit signal for special circumstances
|
||||
if (-transaction.delta > transaction.ctp) {
|
||||
report.cumulativeProfit += (-transaction.delta - transaction.ctp);
|
||||
report.numProfitableActions++;
|
||||
} else if (-transaction.delta < transaction.ctp) {
|
||||
report.cumulativeLoss += (transaction.ctp + transaction.delta);
|
||||
report.numUnprofitableActions++;
|
||||
}
|
||||
break;
|
||||
case TYPE_REGULATION:
|
||||
if (transaction.delta > 0) {
|
||||
report.cumulativePositiveRegulations += transaction.delta;
|
||||
report.numPositiveRegulations++;
|
||||
} else if (transaction.delta < 0) {
|
||||
report.cumulativeNegativeRegulations -= transaction.delta;
|
||||
report.numNegativeRegulations++;
|
||||
}
|
||||
break;
|
||||
case TYPE_REWARD:
|
||||
if (transaction.delta != 0) {
|
||||
report.cumulativeRewards += transaction.delta;
|
||||
report.numRewards++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void tearDown() {
|
||||
for (int i = 0; i < mReports.length; ++i) {
|
||||
mReports[i] = null;
|
||||
}
|
||||
mPeriodIndex = 0;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String padStringWithSpaces(@NonNull String text, int targetLength) {
|
||||
// Make sure to have at least one space on either side.
|
||||
final int padding = Math.max(2, targetLength - text.length()) >>> 1;
|
||||
return " ".repeat(padding) + text + " ".repeat(padding);
|
||||
}
|
||||
|
||||
void dump(IndentingPrintWriter pw) {
|
||||
pw.println("Reports:");
|
||||
pw.increaseIndent();
|
||||
pw.print(" Total Discharge");
|
||||
final int statColsLength = 47;
|
||||
pw.print(padStringWithSpaces("Profit (avg/action : avg/discharge)", statColsLength));
|
||||
pw.print(padStringWithSpaces("Loss (avg/action : avg/discharge)", statColsLength));
|
||||
pw.print(padStringWithSpaces("Rewards (avg/reward : avg/discharge)", statColsLength));
|
||||
pw.print(padStringWithSpaces("+Regs (avg/reg : avg/discharge)", statColsLength));
|
||||
pw.print(padStringWithSpaces("-Regs (avg/reg : avg/discharge)", statColsLength));
|
||||
pw.println();
|
||||
for (int r = 0; r < NUM_PERIODS_TO_RETAIN; ++r) {
|
||||
final int idx = (mPeriodIndex - r + NUM_PERIODS_TO_RETAIN) % NUM_PERIODS_TO_RETAIN;
|
||||
final Report report = mReports[idx];
|
||||
if (report == null) {
|
||||
continue;
|
||||
}
|
||||
pw.print("t-");
|
||||
pw.print(r);
|
||||
pw.print(": ");
|
||||
pw.print(padStringWithSpaces(Integer.toString(report.cumulativeBatteryDischarge), 15));
|
||||
if (report.numProfitableActions > 0) {
|
||||
final String perDischarge = report.cumulativeBatteryDischarge > 0
|
||||
? cakeToString(report.cumulativeProfit / report.cumulativeBatteryDischarge)
|
||||
: "N/A";
|
||||
pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
|
||||
cakeToString(report.cumulativeProfit),
|
||||
cakeToString(report.cumulativeProfit / report.numProfitableActions),
|
||||
perDischarge),
|
||||
statColsLength));
|
||||
} else {
|
||||
pw.print(padStringWithSpaces("N/A", statColsLength));
|
||||
}
|
||||
if (report.numUnprofitableActions > 0) {
|
||||
final String perDischarge = report.cumulativeBatteryDischarge > 0
|
||||
? cakeToString(report.cumulativeLoss / report.cumulativeBatteryDischarge)
|
||||
: "N/A";
|
||||
pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
|
||||
cakeToString(report.cumulativeLoss),
|
||||
cakeToString(report.cumulativeLoss / report.numUnprofitableActions),
|
||||
perDischarge),
|
||||
statColsLength));
|
||||
} else {
|
||||
pw.print(padStringWithSpaces("N/A", statColsLength));
|
||||
}
|
||||
if (report.numRewards > 0) {
|
||||
final String perDischarge = report.cumulativeBatteryDischarge > 0
|
||||
? cakeToString(report.cumulativeRewards / report.cumulativeBatteryDischarge)
|
||||
: "N/A";
|
||||
pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
|
||||
cakeToString(report.cumulativeRewards),
|
||||
cakeToString(report.cumulativeRewards / report.numRewards),
|
||||
perDischarge),
|
||||
statColsLength));
|
||||
} else {
|
||||
pw.print(padStringWithSpaces("N/A", statColsLength));
|
||||
}
|
||||
if (report.numPositiveRegulations > 0) {
|
||||
final String perDischarge = report.cumulativeBatteryDischarge > 0
|
||||
? cakeToString(
|
||||
report.cumulativePositiveRegulations / report.cumulativeBatteryDischarge)
|
||||
: "N/A";
|
||||
pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
|
||||
cakeToString(report.cumulativePositiveRegulations),
|
||||
cakeToString(report.cumulativePositiveRegulations
|
||||
/ report.numPositiveRegulations),
|
||||
perDischarge),
|
||||
statColsLength));
|
||||
} else {
|
||||
pw.print(padStringWithSpaces("N/A", statColsLength));
|
||||
}
|
||||
if (report.numNegativeRegulations > 0) {
|
||||
final String perDischarge = report.cumulativeBatteryDischarge > 0
|
||||
? cakeToString(
|
||||
report.cumulativeNegativeRegulations / report.cumulativeBatteryDischarge)
|
||||
: "N/A";
|
||||
pw.print(padStringWithSpaces(String.format("%s (%s : %s)",
|
||||
cakeToString(report.cumulativeNegativeRegulations),
|
||||
cakeToString(report.cumulativeNegativeRegulations
|
||||
/ report.numNegativeRegulations),
|
||||
perDischarge),
|
||||
statColsLength));
|
||||
} else {
|
||||
pw.print(padStringWithSpaces("N/A", statColsLength));
|
||||
}
|
||||
pw.println();
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,7 @@ public class InternalResourceService extends SystemService {
|
||||
private IDeviceIdleController mDeviceIdleController;
|
||||
|
||||
private final Agent mAgent;
|
||||
private final Analyst mAnalyst;
|
||||
private final ConfigObserver mConfigObserver;
|
||||
private final EconomyManagerStub mEconomyManagerStub;
|
||||
private final Scribe mScribe;
|
||||
@@ -254,9 +255,10 @@ public class InternalResourceService extends SystemService {
|
||||
mPackageManager = context.getPackageManager();
|
||||
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
|
||||
mEconomyManagerStub = new EconomyManagerStub();
|
||||
mScribe = new Scribe(this);
|
||||
mAnalyst = new Analyst();
|
||||
mScribe = new Scribe(this, mAnalyst);
|
||||
mCompleteEconomicPolicy = new CompleteEconomicPolicy(this);
|
||||
mAgent = new Agent(this, mScribe);
|
||||
mAgent = new Agent(this, mScribe, mAnalyst);
|
||||
|
||||
mConfigObserver = new ConfigObserver(mHandler, context);
|
||||
|
||||
@@ -375,6 +377,7 @@ public class InternalResourceService extends SystemService {
|
||||
void onBatteryLevelChanged() {
|
||||
synchronized (mLock) {
|
||||
final int newBatteryLevel = getCurrentBatteryLevel();
|
||||
mAnalyst.noteBatteryLevelChange(newBatteryLevel);
|
||||
final boolean increased = newBatteryLevel > mCurrentBatteryLevel;
|
||||
if (increased) {
|
||||
mAgent.distributeBasicIncomeLocked(newBatteryLevel);
|
||||
@@ -741,6 +744,7 @@ public class InternalResourceService extends SystemService {
|
||||
}
|
||||
synchronized (mLock) {
|
||||
mAgent.tearDownLocked();
|
||||
mAnalyst.tearDown();
|
||||
mCompleteEconomicPolicy.tearDown();
|
||||
mExemptedApps.clear();
|
||||
mExemptListLoaded = false;
|
||||
@@ -1145,6 +1149,9 @@ public class InternalResourceService extends SystemService {
|
||||
|
||||
pw.println();
|
||||
mAgent.dumpLocked(pw);
|
||||
|
||||
pw.println();
|
||||
mAnalyst.dump(pw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ public class Scribe {
|
||||
private static final String XML_TAG_TARE = "tare";
|
||||
private static final String XML_TAG_TRANSACTION = "transaction";
|
||||
private static final String XML_TAG_USER = "user";
|
||||
private static final String XML_TAG_PERIOD_REPORT = "report";
|
||||
|
||||
private static final String XML_ATTR_CTP = "ctp";
|
||||
private static final String XML_ATTR_DELTA = "delta";
|
||||
@@ -86,6 +87,18 @@ public class Scribe {
|
||||
private static final String XML_ATTR_LAST_RECLAMATION_TIME = "lastReclamationTime";
|
||||
private static final String XML_ATTR_REMAINING_CONSUMABLE_CAKES = "remainingConsumableCakes";
|
||||
private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit";
|
||||
private static final String XML_ATTR_PR_DISCHARGE = "discharge";
|
||||
private static final String XML_ATTR_PR_BATTERY_LEVEL = "batteryLevel";
|
||||
private static final String XML_ATTR_PR_PROFIT = "profit";
|
||||
private static final String XML_ATTR_PR_NUM_PROFIT = "numProfits";
|
||||
private static final String XML_ATTR_PR_LOSS = "loss";
|
||||
private static final String XML_ATTR_PR_NUM_LOSS = "numLoss";
|
||||
private static final String XML_ATTR_PR_REWARDS = "rewards";
|
||||
private static final String XML_ATTR_PR_NUM_REWARDS = "numRewards";
|
||||
private static final String XML_ATTR_PR_POS_REGULATIONS = "posRegulations";
|
||||
private static final String XML_ATTR_PR_NUM_POS_REGULATIONS = "numPosRegulations";
|
||||
private static final String XML_ATTR_PR_NEG_REGULATIONS = "negRegulations";
|
||||
private static final String XML_ATTR_PR_NUM_NEG_REGULATIONS = "numNegRegulations";
|
||||
|
||||
/** Version of the file schema. */
|
||||
private static final int STATE_FILE_VERSION = 0;
|
||||
@@ -94,6 +107,7 @@ public class Scribe {
|
||||
|
||||
private final AtomicFile mStateFile;
|
||||
private final InternalResourceService mIrs;
|
||||
private final Analyst mAnalyst;
|
||||
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
private long mLastReclamationTime;
|
||||
@@ -107,13 +121,14 @@ public class Scribe {
|
||||
private final Runnable mCleanRunnable = this::cleanupLedgers;
|
||||
private final Runnable mWriteRunnable = this::writeState;
|
||||
|
||||
Scribe(InternalResourceService irs) {
|
||||
this(irs, Environment.getDataSystemDirectory());
|
||||
Scribe(InternalResourceService irs, Analyst analyst) {
|
||||
this(irs, analyst, Environment.getDataSystemDirectory());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Scribe(InternalResourceService irs, File dataDir) {
|
||||
Scribe(InternalResourceService irs, Analyst analyst, File dataDir) {
|
||||
mIrs = irs;
|
||||
mAnalyst = analyst;
|
||||
|
||||
final File tareDir = new File(dataDir, "tare");
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
@@ -210,6 +225,7 @@ public class Scribe {
|
||||
}
|
||||
}
|
||||
|
||||
final List<Analyst.Report> reports = new ArrayList<>();
|
||||
try (FileInputStream fis = mStateFile.openRead()) {
|
||||
TypedXmlPullParser parser = Xml.resolvePullParser(fis);
|
||||
|
||||
@@ -263,11 +279,15 @@ public class Scribe {
|
||||
readUserFromXmlLocked(
|
||||
parser, installedPackagesPerUser, endTimeCutoff));
|
||||
break;
|
||||
case XML_TAG_PERIOD_REPORT:
|
||||
reports.add(readReportFromXml(parser));
|
||||
break;
|
||||
default:
|
||||
Slog.e(TAG, "Unexpected tag: " + tagName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mAnalyst.loadReports(reports);
|
||||
scheduleCleanup(earliestEndTime);
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
Slog.wtf(TAG, "Error reading state from disk", e);
|
||||
@@ -457,6 +477,37 @@ public class Scribe {
|
||||
return earliestEndTime;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param parser Xml parser at the beginning of a {@link #XML_TAG_PERIOD_REPORT} tag. The next
|
||||
* "parser.next()" call will take the parser into the body of the report tag.
|
||||
* @return Newly instantiated Report holding all the information we just read out of the xml tag
|
||||
*/
|
||||
@NonNull
|
||||
private static Analyst.Report readReportFromXml(TypedXmlPullParser parser)
|
||||
throws XmlPullParserException, IOException {
|
||||
final Analyst.Report report = new Analyst.Report();
|
||||
|
||||
report.cumulativeBatteryDischarge = parser.getAttributeInt(null, XML_ATTR_PR_DISCHARGE);
|
||||
report.currentBatteryLevel = parser.getAttributeInt(null, XML_ATTR_PR_BATTERY_LEVEL);
|
||||
report.cumulativeProfit = parser.getAttributeLong(null, XML_ATTR_PR_PROFIT);
|
||||
report.numProfitableActions = parser.getAttributeInt(null, XML_ATTR_PR_NUM_PROFIT);
|
||||
report.cumulativeLoss = parser.getAttributeLong(null, XML_ATTR_PR_LOSS);
|
||||
report.numUnprofitableActions = parser.getAttributeInt(null, XML_ATTR_PR_NUM_LOSS);
|
||||
report.cumulativeRewards = parser.getAttributeLong(null, XML_ATTR_PR_REWARDS);
|
||||
report.numRewards = parser.getAttributeInt(null, XML_ATTR_PR_NUM_REWARDS);
|
||||
report.cumulativePositiveRegulations =
|
||||
parser.getAttributeLong(null, XML_ATTR_PR_POS_REGULATIONS);
|
||||
report.numPositiveRegulations =
|
||||
parser.getAttributeInt(null, XML_ATTR_PR_NUM_POS_REGULATIONS);
|
||||
report.cumulativeNegativeRegulations =
|
||||
parser.getAttributeLong(null, XML_ATTR_PR_NEG_REGULATIONS);
|
||||
report.numNegativeRegulations =
|
||||
parser.getAttributeInt(null, XML_ATTR_PR_NUM_NEG_REGULATIONS);
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
private void scheduleCleanup(long earliestEndTime) {
|
||||
if (earliestEndTime == Long.MAX_VALUE) {
|
||||
return;
|
||||
@@ -501,6 +552,11 @@ public class Scribe {
|
||||
writeUserLocked(out, userId));
|
||||
}
|
||||
|
||||
List<Analyst.Report> reports = mAnalyst.getReports();
|
||||
for (int i = 0, size = reports.size(); i < size; ++i) {
|
||||
writeReport(out, reports.get(i));
|
||||
}
|
||||
|
||||
out.endTag(null, XML_TAG_TARE);
|
||||
|
||||
out.endDocument();
|
||||
@@ -560,6 +616,24 @@ public class Scribe {
|
||||
out.endTag(null, XML_TAG_TRANSACTION);
|
||||
}
|
||||
|
||||
private static void writeReport(@NonNull TypedXmlSerializer out,
|
||||
@NonNull Analyst.Report report) throws IOException {
|
||||
out.startTag(null, XML_TAG_PERIOD_REPORT);
|
||||
out.attributeInt(null, XML_ATTR_PR_DISCHARGE, report.cumulativeBatteryDischarge);
|
||||
out.attributeInt(null, XML_ATTR_PR_BATTERY_LEVEL, report.currentBatteryLevel);
|
||||
out.attributeLong(null, XML_ATTR_PR_PROFIT, report.cumulativeProfit);
|
||||
out.attributeInt(null, XML_ATTR_PR_NUM_PROFIT, report.numProfitableActions);
|
||||
out.attributeLong(null, XML_ATTR_PR_LOSS, report.cumulativeLoss);
|
||||
out.attributeInt(null, XML_ATTR_PR_NUM_LOSS, report.numUnprofitableActions);
|
||||
out.attributeLong(null, XML_ATTR_PR_REWARDS, report.cumulativeRewards);
|
||||
out.attributeInt(null, XML_ATTR_PR_NUM_REWARDS, report.numRewards);
|
||||
out.attributeLong(null, XML_ATTR_PR_POS_REGULATIONS, report.cumulativePositiveRegulations);
|
||||
out.attributeInt(null, XML_ATTR_PR_NUM_POS_REGULATIONS, report.numPositiveRegulations);
|
||||
out.attributeLong(null, XML_ATTR_PR_NEG_REGULATIONS, report.cumulativeNegativeRegulations);
|
||||
out.attributeInt(null, XML_ATTR_PR_NUM_NEG_REGULATIONS, report.numNegativeRegulations);
|
||||
out.endTag(null, XML_TAG_PERIOD_REPORT);
|
||||
}
|
||||
|
||||
@GuardedBy("mIrs.getLock()")
|
||||
void dumpLocked(IndentingPrintWriter pw, boolean dumpAll) {
|
||||
pw.println("Ledgers:");
|
||||
|
||||
@@ -46,6 +46,8 @@ public class AgentTest {
|
||||
@Mock
|
||||
private CompleteEconomicPolicy mEconomicPolicy;
|
||||
@Mock
|
||||
private Analyst mAnalyst;
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private InternalResourceService mIrs;
|
||||
@@ -53,8 +55,8 @@ public class AgentTest {
|
||||
private Scribe mScribe;
|
||||
|
||||
private static class MockScribe extends Scribe {
|
||||
MockScribe(InternalResourceService irs) {
|
||||
super(irs);
|
||||
MockScribe(InternalResourceService irs, Analyst analyst) {
|
||||
super(irs, analyst);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,7 +76,7 @@ public class AgentTest {
|
||||
doReturn(mEconomicPolicy).when(mIrs).getCompleteEconomicPolicyLocked();
|
||||
doReturn(mIrs).when(mIrs).getLock();
|
||||
doReturn(mock(AlarmManager.class)).when(mContext).getSystemService(Context.ALARM_SERVICE);
|
||||
mScribe = new MockScribe(mIrs);
|
||||
mScribe = new MockScribe(mIrs, mAnalyst);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -86,7 +88,7 @@ public class AgentTest {
|
||||
|
||||
@Test
|
||||
public void testRecordTransaction_UnderMax() {
|
||||
Agent agent = new Agent(mIrs, mScribe);
|
||||
Agent agent = new Agent(mIrs, mScribe, mAnalyst);
|
||||
Ledger ledger = new Ledger();
|
||||
|
||||
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
|
||||
@@ -115,7 +117,7 @@ public class AgentTest {
|
||||
|
||||
@Test
|
||||
public void testRecordTransaction_MaxConsumptionLimit() {
|
||||
Agent agent = new Agent(mIrs, mScribe);
|
||||
Agent agent = new Agent(mIrs, mScribe, mAnalyst);
|
||||
Ledger ledger = new Ledger();
|
||||
|
||||
doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
|
||||
@@ -162,7 +164,7 @@ public class AgentTest {
|
||||
|
||||
@Test
|
||||
public void testRecordTransaction_MaxSatiatedBalance() {
|
||||
Agent agent = new Agent(mIrs, mScribe);
|
||||
Agent agent = new Agent(mIrs, mScribe, mAnalyst);
|
||||
Ledger ledger = new Ledger();
|
||||
|
||||
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package com.android.server.tare;
|
||||
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -41,6 +41,8 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoSession;
|
||||
import org.mockito.quality.Strictness;
|
||||
@@ -66,7 +68,10 @@ public class ScribeTest {
|
||||
private Scribe mScribeUnderTest;
|
||||
private File mTestFileDir;
|
||||
private final List<PackageInfo> mInstalledPackages = new ArrayList<>();
|
||||
private final List<Analyst.Report> mReports = new ArrayList<>();
|
||||
|
||||
@Mock
|
||||
private Analyst mAnalyst;
|
||||
@Mock
|
||||
private InternalResourceService mIrs;
|
||||
|
||||
@@ -84,11 +89,12 @@ public class ScribeTest {
|
||||
when(mIrs.getLock()).thenReturn(new Object());
|
||||
when(mIrs.isEnabled()).thenReturn(true);
|
||||
when(mIrs.getInstalledPackages()).thenReturn(mInstalledPackages);
|
||||
when(mAnalyst.getReports()).thenReturn(mReports);
|
||||
mTestFileDir = new File(getContext().getFilesDir(), "scribe_test");
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
mTestFileDir.mkdirs();
|
||||
Log.d(TAG, "Saving data to '" + mTestFileDir + "'");
|
||||
mScribeUnderTest = new Scribe(mIrs, mTestFileDir);
|
||||
mScribeUnderTest = new Scribe(mIrs, mAnalyst, mTestFileDir);
|
||||
|
||||
addInstalledPackage(TEST_USER_ID, TEST_PACKAGE);
|
||||
}
|
||||
@@ -104,6 +110,62 @@ public class ScribeTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWritingAnalystReportsToDisk() {
|
||||
ArgumentCaptor<List<Analyst.Report>> reportCaptor =
|
||||
ArgumentCaptor.forClass(List.class);
|
||||
|
||||
InOrder inOrder = inOrder(mAnalyst);
|
||||
|
||||
// Empty set
|
||||
mReports.clear();
|
||||
mScribeUnderTest.writeImmediatelyForTesting();
|
||||
mScribeUnderTest.loadFromDiskLocked();
|
||||
inOrder.verify(mAnalyst).loadReports(reportCaptor.capture());
|
||||
List<Analyst.Report> result = reportCaptor.getValue();
|
||||
assertReportListsEqual(mReports, result);
|
||||
|
||||
Analyst.Report report1 = new Analyst.Report();
|
||||
report1.cumulativeBatteryDischarge = 1;
|
||||
report1.currentBatteryLevel = 2;
|
||||
report1.cumulativeProfit = 3;
|
||||
report1.numProfitableActions = 4;
|
||||
report1.cumulativeLoss = 5;
|
||||
report1.numUnprofitableActions = 6;
|
||||
report1.cumulativeRewards = 7;
|
||||
report1.numRewards = 8;
|
||||
report1.cumulativePositiveRegulations = 9;
|
||||
report1.numPositiveRegulations = 10;
|
||||
report1.cumulativeNegativeRegulations = 11;
|
||||
report1.numNegativeRegulations = 12;
|
||||
mReports.add(report1);
|
||||
mScribeUnderTest.writeImmediatelyForTesting();
|
||||
mScribeUnderTest.loadFromDiskLocked();
|
||||
inOrder.verify(mAnalyst).loadReports(reportCaptor.capture());
|
||||
result = reportCaptor.getValue();
|
||||
assertReportListsEqual(mReports, result);
|
||||
|
||||
Analyst.Report report2 = new Analyst.Report();
|
||||
report2.cumulativeBatteryDischarge = 10;
|
||||
report2.currentBatteryLevel = 20;
|
||||
report2.cumulativeProfit = 30;
|
||||
report2.numProfitableActions = 40;
|
||||
report2.cumulativeLoss = 50;
|
||||
report2.numUnprofitableActions = 60;
|
||||
report2.cumulativeRewards = 70;
|
||||
report2.numRewards = 80;
|
||||
report2.cumulativePositiveRegulations = 90;
|
||||
report2.numPositiveRegulations = 100;
|
||||
report2.cumulativeNegativeRegulations = 110;
|
||||
report2.numNegativeRegulations = 120;
|
||||
mReports.add(report2);
|
||||
mScribeUnderTest.writeImmediatelyForTesting();
|
||||
mScribeUnderTest.loadFromDiskLocked();
|
||||
inOrder.verify(mAnalyst).loadReports(reportCaptor.capture());
|
||||
result = reportCaptor.getValue();
|
||||
assertReportListsEqual(mReports, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteHighLevelStateToDisk() {
|
||||
long lastReclamationTime = System.currentTimeMillis();
|
||||
@@ -277,6 +339,49 @@ public class ScribeTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void assertReportListsEqual(List<Analyst.Report> expected,
|
||||
List<Analyst.Report> actual) {
|
||||
if (expected == null) {
|
||||
assertNull(actual);
|
||||
return;
|
||||
}
|
||||
assertNotNull(actual);
|
||||
assertEquals(expected.size(), actual.size());
|
||||
for (int i = 0; i < expected.size(); ++i) {
|
||||
Analyst.Report eReport = expected.get(i);
|
||||
Analyst.Report aReport = actual.get(i);
|
||||
if (eReport == null) {
|
||||
assertNull(aReport);
|
||||
continue;
|
||||
}
|
||||
assertNotNull(aReport);
|
||||
assertEquals("Reports #" + i + " cumulativeBatteryDischarge are not equal",
|
||||
eReport.cumulativeBatteryDischarge, aReport.cumulativeBatteryDischarge);
|
||||
assertEquals("Reports #" + i + " currentBatteryLevel are not equal",
|
||||
eReport.currentBatteryLevel, aReport.currentBatteryLevel);
|
||||
assertEquals("Reports #" + i + " cumulativeProfit are not equal",
|
||||
eReport.cumulativeProfit, aReport.cumulativeProfit);
|
||||
assertEquals("Reports #" + i + " numProfitableActions are not equal",
|
||||
eReport.numProfitableActions, aReport.numProfitableActions);
|
||||
assertEquals("Reports #" + i + " cumulativeLoss are not equal",
|
||||
eReport.cumulativeLoss, aReport.cumulativeLoss);
|
||||
assertEquals("Reports #" + i + " numUnprofitableActions are not equal",
|
||||
eReport.numUnprofitableActions, aReport.numUnprofitableActions);
|
||||
assertEquals("Reports #" + i + " cumulativeRewards are not equal",
|
||||
eReport.cumulativeRewards, aReport.cumulativeRewards);
|
||||
assertEquals("Reports #" + i + " numRewards are not equal",
|
||||
eReport.numRewards, aReport.numRewards);
|
||||
assertEquals("Reports #" + i + " cumulativePositiveRegulations are not equal",
|
||||
eReport.cumulativePositiveRegulations, aReport.cumulativePositiveRegulations);
|
||||
assertEquals("Reports #" + i + " numPositiveRegulations are not equal",
|
||||
eReport.numPositiveRegulations, aReport.numPositiveRegulations);
|
||||
assertEquals("Reports #" + i + " cumulativeNegativeRegulations are not equal",
|
||||
eReport.cumulativeNegativeRegulations, aReport.cumulativeNegativeRegulations);
|
||||
assertEquals("Reports #" + i + " numNegativeRegulations are not equal",
|
||||
eReport.numNegativeRegulations, aReport.numNegativeRegulations);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertTransactionsEqual(Ledger.Transaction expected, Ledger.Transaction actual) {
|
||||
if (expected == null) {
|
||||
assertNull(actual);
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.tare;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Test that the Analyst processes transactions correctly. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class AnalystTest {
|
||||
|
||||
@Test
|
||||
public void testInitialState() {
|
||||
final Analyst analyst = new Analyst();
|
||||
assertEquals(0, analyst.getReports().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatteryLevelChange() {
|
||||
final Analyst analyst = new Analyst();
|
||||
|
||||
Analyst.Report expected = new Analyst.Report();
|
||||
expected.currentBatteryLevel = 55;
|
||||
analyst.noteBatteryLevelChange(55);
|
||||
assertEquals(1, analyst.getReports().size());
|
||||
assertReportsEqual(expected, analyst.getReports().get(0));
|
||||
|
||||
// Discharging
|
||||
analyst.noteBatteryLevelChange(54);
|
||||
expected.currentBatteryLevel = 54;
|
||||
expected.cumulativeBatteryDischarge = 1;
|
||||
assertEquals(1, analyst.getReports().size());
|
||||
assertReportsEqual(expected, analyst.getReports().get(0));
|
||||
analyst.noteBatteryLevelChange(50);
|
||||
expected.currentBatteryLevel = 50;
|
||||
expected.cumulativeBatteryDischarge = 5;
|
||||
assertEquals(1, analyst.getReports().size());
|
||||
assertReportsEqual(expected, analyst.getReports().get(0));
|
||||
|
||||
// Charging
|
||||
analyst.noteBatteryLevelChange(51);
|
||||
expected.currentBatteryLevel = 51;
|
||||
assertEquals(1, analyst.getReports().size());
|
||||
assertReportsEqual(expected, analyst.getReports().get(0));
|
||||
analyst.noteBatteryLevelChange(55);
|
||||
expected.currentBatteryLevel = 55;
|
||||
assertEquals(1, analyst.getReports().size());
|
||||
assertReportsEqual(expected, analyst.getReports().get(0));
|
||||
|
||||
// Reset
|
||||
analyst.noteBatteryLevelChange(100);
|
||||
assertEquals(2, analyst.getReports().size());
|
||||
assertReportsEqual(expected, analyst.getReports().get(0));
|
||||
expected.currentBatteryLevel = 100;
|
||||
expected.cumulativeBatteryDischarge = 0;
|
||||
assertReportsEqual(expected, analyst.getReports().get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransaction() {
|
||||
runTestTransactions(new Analyst(), new Analyst.Report(), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransaction_PeriodChange() {
|
||||
final Analyst analyst = new Analyst();
|
||||
|
||||
Analyst.Report expected = new Analyst.Report();
|
||||
expected.currentBatteryLevel = 55;
|
||||
analyst.noteBatteryLevelChange(55);
|
||||
|
||||
runTestTransactions(analyst, expected, 1);
|
||||
|
||||
expected.currentBatteryLevel = 49;
|
||||
expected.cumulativeBatteryDischarge = 6;
|
||||
analyst.noteBatteryLevelChange(49);
|
||||
|
||||
runTestTransactions(analyst, expected, 1);
|
||||
|
||||
expected = new Analyst.Report();
|
||||
expected.currentBatteryLevel = 100;
|
||||
analyst.noteBatteryLevelChange(100);
|
||||
expected.cumulativeBatteryDischarge = 0;
|
||||
|
||||
runTestTransactions(analyst, expected, 2);
|
||||
}
|
||||
|
||||
private void runTestTransactions(Analyst analyst, Analyst.Report lastExpectedReport,
|
||||
int numExpectedReports) {
|
||||
Analyst.Report expected = lastExpectedReport;
|
||||
|
||||
// Profit
|
||||
analyst.noteTransaction(
|
||||
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_ACTION, null, -51, 1));
|
||||
expected.cumulativeProfit += 50;
|
||||
expected.numProfitableActions += 1;
|
||||
assertEquals(numExpectedReports, analyst.getReports().size());
|
||||
assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
|
||||
|
||||
// Loss
|
||||
analyst.noteTransaction(
|
||||
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_ACTION, null, -51, 100));
|
||||
expected.cumulativeLoss += 49;
|
||||
expected.numUnprofitableActions += 1;
|
||||
assertEquals(numExpectedReports, analyst.getReports().size());
|
||||
assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
|
||||
|
||||
// Reward
|
||||
analyst.noteTransaction(
|
||||
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD, null, 51, 0));
|
||||
expected.cumulativeRewards += 51;
|
||||
expected.numRewards += 1;
|
||||
assertEquals(numExpectedReports, analyst.getReports().size());
|
||||
assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
|
||||
|
||||
// Regulations
|
||||
analyst.noteTransaction(
|
||||
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REGULATION, null, 25, 0));
|
||||
expected.cumulativePositiveRegulations += 25;
|
||||
expected.numPositiveRegulations += 1;
|
||||
assertEquals(numExpectedReports, analyst.getReports().size());
|
||||
assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
|
||||
analyst.noteTransaction(
|
||||
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REGULATION, null, -25, 0));
|
||||
expected.cumulativeNegativeRegulations += 25;
|
||||
expected.numNegativeRegulations += 1;
|
||||
assertEquals(numExpectedReports, analyst.getReports().size());
|
||||
assertReportsEqual(expected, analyst.getReports().get(numExpectedReports - 1));
|
||||
|
||||
// No-ops
|
||||
analyst.noteTransaction(
|
||||
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_ACTION, null, -100, 100));
|
||||
analyst.noteTransaction(
|
||||
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REGULATION, null, 0, 0));
|
||||
analyst.noteTransaction(
|
||||
new Ledger.Transaction(0, 1000, EconomicPolicy.TYPE_REWARD, null, 0, 0));
|
||||
assertEquals(numExpectedReports, analyst.getReports().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadReports() {
|
||||
final Analyst analyst = new Analyst();
|
||||
|
||||
List<Analyst.Report> expected = new ArrayList<>();
|
||||
analyst.loadReports(expected);
|
||||
assertReportListsEqual(expected, analyst.getReports());
|
||||
|
||||
Analyst.Report report1 = new Analyst.Report();
|
||||
report1.cumulativeBatteryDischarge = 1;
|
||||
report1.currentBatteryLevel = 2;
|
||||
report1.cumulativeProfit = 3;
|
||||
report1.numProfitableActions = 4;
|
||||
report1.cumulativeLoss = 5;
|
||||
report1.numUnprofitableActions = 6;
|
||||
report1.cumulativeRewards = 7;
|
||||
report1.numRewards = 8;
|
||||
report1.cumulativePositiveRegulations = 9;
|
||||
report1.numPositiveRegulations = 10;
|
||||
report1.cumulativeNegativeRegulations = 11;
|
||||
report1.numNegativeRegulations = 12;
|
||||
expected.add(report1);
|
||||
analyst.loadReports(expected);
|
||||
assertReportListsEqual(expected, analyst.getReports());
|
||||
|
||||
Analyst.Report report2 = new Analyst.Report();
|
||||
report2.cumulativeBatteryDischarge = 10;
|
||||
report2.currentBatteryLevel = 20;
|
||||
report2.cumulativeProfit = 30;
|
||||
report2.numProfitableActions = 40;
|
||||
report2.cumulativeLoss = 50;
|
||||
report2.numUnprofitableActions = 60;
|
||||
report2.cumulativeRewards = 70;
|
||||
report2.numRewards = 80;
|
||||
report2.cumulativePositiveRegulations = 90;
|
||||
report2.numPositiveRegulations = 100;
|
||||
report2.cumulativeNegativeRegulations = 110;
|
||||
report2.numNegativeRegulations = 120;
|
||||
expected.add(report2);
|
||||
analyst.loadReports(expected);
|
||||
assertReportListsEqual(expected, analyst.getReports());
|
||||
}
|
||||
|
||||
private void assertReportsEqual(Analyst.Report expected, Analyst.Report actual) {
|
||||
if (expected == null) {
|
||||
assertNull(actual);
|
||||
return;
|
||||
}
|
||||
assertNotNull(actual);
|
||||
assertEquals(expected.cumulativeBatteryDischarge, actual.cumulativeBatteryDischarge);
|
||||
assertEquals(expected.currentBatteryLevel, actual.currentBatteryLevel);
|
||||
assertEquals(expected.cumulativeProfit, actual.cumulativeProfit);
|
||||
assertEquals(expected.numProfitableActions, actual.numProfitableActions);
|
||||
assertEquals(expected.cumulativeLoss, actual.cumulativeLoss);
|
||||
assertEquals(expected.numUnprofitableActions, actual.numUnprofitableActions);
|
||||
assertEquals(expected.cumulativeRewards, actual.cumulativeRewards);
|
||||
assertEquals(expected.numRewards, actual.numRewards);
|
||||
assertEquals(expected.cumulativePositiveRegulations, actual.cumulativePositiveRegulations);
|
||||
assertEquals(expected.numPositiveRegulations, actual.numPositiveRegulations);
|
||||
assertEquals(expected.cumulativeNegativeRegulations, actual.cumulativeNegativeRegulations);
|
||||
assertEquals(expected.numNegativeRegulations, actual.numNegativeRegulations);
|
||||
}
|
||||
|
||||
private void assertReportListsEqual(List<Analyst.Report> expected,
|
||||
List<Analyst.Report> actual) {
|
||||
if (expected == null) {
|
||||
assertNull(actual);
|
||||
return;
|
||||
}
|
||||
assertNotNull(actual);
|
||||
assertEquals(expected.size(), actual.size());
|
||||
for (int i = 0; i < expected.size(); ++i) {
|
||||
assertReportsEqual(expected.get(i), actual.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user