Track basic TARE statistics.
Track some basic statistics to see how TARE performs over time. They'll only be kept and viewable locally. We can continue to refine them to determine what's useful and necessary. Bug: 158300259 Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/tare Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/tare Change-Id: Ia408b9fc2b688a850fe8974ac473613711711a15
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