blob: f187e028a127c517e8e1c670d07cb6dc0b3ca579 [file] [log] [blame]
/*
* Copyright (C) 2018 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.helpers;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.when;
import android.os.nano.OsProtoEnums;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import com.android.os.nano.AtomsProto;
import com.android.os.nano.StatsLog;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Android Unit tests for {@link com.android.helpers.ThermalHelper}.
*
* <p>To run this test: Disable SELinux with "adb shell setenforce 0"; if this command fails with
* "Permission denied", try running "adb shell su 0 setenforce 0". Then run: "atest
* CollectorsHelperTest:com.android.helpers.ThermalHelperTest".
*/
@RunWith(AndroidJUnit4.class)
public class ThermalHelperTest {
private static final String THROTTLING_KEY =
MetricUtility.constructKey("thermal", "throttling", "severity");
private static final String FAKE_SERVICE_DUMP = "F\nA\nK\nE\nThermal Status: 2\nO\nK";
private static final String SERVICE_DUMP_TEMPLATE =
"IsStatusOverride: false\n"
+ "ThermalEventListeners:\n"
+ " callbacks: 1\n"
+ " killed: false\n"
+ " broadcasts count: -1\n"
+ "ThermalStatusListeners:\n"
+ " callbacks: 1\n"
+ " killed: false\n"
+ " broadcasts count: -1\n"
+ "Thermal Status: 0\n"
+ "Cached temperatures:\n"
+ " Temperature{mValue=45.67, mType=3, mName=cached temperature sensor,"
+ " mStatus=0}\n"
+ "HAL Ready: true\n"
+ "HAL connection:\n"
+ " ThermalHAL 2.0 connected: yes\n"
+ "Current temperatures from HAL:\n"
+ " Temperature{mValue=%s, mType=3, mName=%s, mStatus=0}\n"
+ " Temperature{mValue=45.6, mType=3, mName=test temperature sensor2,"
+ " mStatus=0}\n"
+ " Temperature{mValue=56.789, mType=3, mName=test temperature sensor3,"
+ " mStatus=0}\n"
+ "Current cooling devices from HAL:\n"
+ " CoolingDevice{mValue=100, mType=0, mName=test cooling device}";
private ThermalHelper mThermalHelper;
private StatsdHelper mStatsdHelper;
private UiDevice mDevice;
@Before
public void setUp() throws Exception {
mThermalHelper = new ThermalHelper();
// Set up the statsd helper to mock statsd calls.
mStatsdHelper = Mockito.spy(new StatsdHelper());
mThermalHelper.setStatsdHelper(mStatsdHelper);
// Set up the fake device for mocking shell commands.
mDevice = Mockito.mock(UiDevice.class);
mThermalHelper.setUiDevice(mDevice);
when(mDevice.executeShellCommand("dumpsys thermalservice")).thenReturn(FAKE_SERVICE_DUMP);
}
/** Test registering and unregistering the thermal config. */
@Test
public void testThermalConfigRegistration() throws Exception {
assertTrue(mThermalHelper.startCollecting());
assertTrue(mThermalHelper.stopCollecting());
}
/** Test registering the thermal config fails when no initial throttling value is found. */
@Test
public void testThermalConfigRegistration_noInitialValue() throws Exception {
when(mDevice.executeShellCommand("dumpsys thermalservice")).thenReturn("FAKE RESPONSE");
assertFalse(mThermalHelper.startCollecting());
}
/** Test that only the initial value shows up when there are no events. */
@Test
public void testInitialMetricsWithoutEvents() throws Exception {
when(mStatsdHelper.getEventMetrics()).thenReturn(new ArrayList<StatsLog.EventMetricData>());
assertTrue(mThermalHelper.startCollecting());
assertEquals(
mThermalHelper.getMetrics().get(THROTTLING_KEY).toString(),
String.valueOf(OsProtoEnums.MODERATE));
assertTrue(mThermalHelper.stopCollecting());
}
/** Test that the initial value and a single event show up from a single metric event. */
@Test
public void testSingleEvent() throws Exception {
when(mStatsdHelper.getEventMetrics())
.thenReturn(
getFakeEventMetrics(
getThermalThrottlingSeverityStateChangedEvent(
OsProtoEnums.TEMPERATURE_TYPE_SKIN,
"sensor_name",
OsProtoEnums.LIGHT)));
assertTrue(mThermalHelper.startCollecting());
Map<String, StringBuilder> metrics = mThermalHelper.getMetrics();
assertEquals(
metrics.get(THROTTLING_KEY).toString(),
String.join(
",",
String.valueOf(OsProtoEnums.MODERATE),
String.valueOf(OsProtoEnums.LIGHT)));
assertTrue(mThermalHelper.stopCollecting());
}
/** Test that multiple throttling events with different sources show up together. */
@Test
public void testMultipleDifferentEvents() throws Exception {
when(mStatsdHelper.getEventMetrics())
.thenReturn(
getFakeEventMetrics(
getThermalThrottlingSeverityStateChangedEvent(
OsProtoEnums.TEMPERATURE_TYPE_SKIN,
"sensor1_name",
OsProtoEnums.LIGHT),
getThermalThrottlingSeverityStateChangedEvent(
OsProtoEnums.TEMPERATURE_TYPE_CPU,
"sensor2_name",
OsProtoEnums.MODERATE),
getThermalThrottlingSeverityStateChangedEvent(
OsProtoEnums.TEMPERATURE_TYPE_GPU,
"sensor3_name",
OsProtoEnums.NONE)));
assertTrue(mThermalHelper.startCollecting());
Map<String, StringBuilder> metrics = mThermalHelper.getMetrics();
assertEquals(
metrics.get(THROTTLING_KEY).toString(),
String.join(
",",
String.valueOf(OsProtoEnums.MODERATE),
String.valueOf(OsProtoEnums.LIGHT),
String.valueOf(OsProtoEnums.MODERATE),
String.valueOf(OsProtoEnums.NONE)));
assertTrue(mThermalHelper.stopCollecting());
}
/** Test that the temperature section is parsed correctly. */
@Test
public void testParseTemperature() throws Exception {
// Use real data for this test. It should work everywhere, except for
// aosp_cf_x86_64_phone-userdebug.
mThermalHelper = new ThermalHelper();
mThermalHelper.setStatsdHelper(mStatsdHelper);
assertTrue(mThermalHelper.startCollecting());
Map<String, StringBuilder> metrics = mThermalHelper.getMetrics();
// Validate at least 2 temperature keys exist with all 3 metrics.
int statusMetricsFound = 0;
int valueMetricsFound = 0;
int typeMetricsFound = 0;
for (String key : metrics.keySet()) {
if (!key.startsWith("temperature")) {
continue;
}
if (key.endsWith("status")) {
statusMetricsFound++;
} else if (key.endsWith("value")) {
valueMetricsFound++;
} else if (key.endsWith("type")) {
typeMetricsFound++;
}
}
assertTrue(
"Didn't find at least 2 status, value, and type temperature metrics.",
statusMetricsFound >= 2 && valueMetricsFound >= 2 && typeMetricsFound >= 2);
assertTrue(mThermalHelper.stopCollecting());
}
/** Test that the mock temperature section is parsed correctly. */
@Test
public void testParseTemperatureMock() throws Exception {
// Use mock data for this test.
final String correctName = "test temperature sensor";
final double correctValue = 32.1;
final String correctOutput =
String.format(SERVICE_DUMP_TEMPLATE, String.valueOf(correctValue), correctName);
when(mDevice.executeShellCommand(ThermalHelper.DUMP_THERMALSERVICE_CMD))
.thenReturn(correctOutput);
Map<String, StringBuilder> metrics = mThermalHelper.getMetrics();
// Validate at least 2 temperature keys exist with all 3 metrics.
int statusMetricsFound = 0;
int valueMetricsFound = 0;
int typeMetricsFound = 0;
for (String key : metrics.keySet()) {
if (!key.startsWith("temperature")) {
continue;
}
if (key.endsWith("status")) {
statusMetricsFound++;
} else if (key.endsWith("value")) {
valueMetricsFound++;
} else if (key.endsWith("type")) {
typeMetricsFound++;
}
}
assertTrue(
"Didn't find at least 2 status, value, and type temperature metrics.",
statusMetricsFound >= 2 && valueMetricsFound >= 2 && typeMetricsFound >= 2);
}
/** Test getting temperature value from DUT correctly. */
@Test
public void testGetTemperature() throws Exception {
final double THRESHOLD = 0.0001;
final String correctName = "test temperature sensor";
final double correctValue = 32.1;
final String correctOutput =
String.format(SERVICE_DUMP_TEMPLATE, String.valueOf(correctValue), correctName);
when(mDevice.executeShellCommand(ThermalHelper.DUMP_THERMALSERVICE_CMD))
.thenReturn(correctOutput);
assertTrue(Math.abs(mThermalHelper.getTemperature(correctName) - correctValue) < THRESHOLD);
}
/** Test failing to get temperature value from DUT. */
@Test
public void testGetTemperatureFailed() throws Exception {
final String correctName = "test temperature sensor";
final double correctValue = 32.1;
final String correctOutput =
String.format(SERVICE_DUMP_TEMPLATE, String.valueOf(correctValue), correctName);
final String expectedMessage = "Failed to get temperature of ";
final String badName = "bad temperature sensor";
when(mDevice.executeShellCommand(ThermalHelper.DUMP_THERMALSERVICE_CMD))
.thenReturn(correctOutput);
Exception exception1 =
assertThrows(
IllegalArgumentException.class,
() -> {
mThermalHelper.getTemperature(badName);
});
assertTrue(exception1.getMessage().contains(expectedMessage));
final String badOutput = String.format(SERVICE_DUMP_TEMPLATE, "bad", correctName);
when(mDevice.executeShellCommand(ThermalHelper.DUMP_THERMALSERVICE_CMD))
.thenReturn(badOutput);
Exception exception2 =
assertThrows(
IllegalArgumentException.class,
() -> {
mThermalHelper.getTemperature(correctName);
});
assertTrue(exception2.getMessage().contains(expectedMessage));
final String badOutputEmpty = String.format(SERVICE_DUMP_TEMPLATE, "", correctName);
when(mDevice.executeShellCommand(ThermalHelper.DUMP_THERMALSERVICE_CMD))
.thenReturn(badOutputEmpty);
Exception exception3 =
assertThrows(
IllegalArgumentException.class,
() -> {
mThermalHelper.getTemperature(correctName);
});
assertTrue(exception3.getMessage().contains(expectedMessage));
}
/**
* Returns a list of {@link com.android.os.nano.StatsLog.EventMetricData} that statsd returns.
*/
private List<StatsLog.EventMetricData> getFakeEventMetrics(
AtomsProto.ThermalThrottlingSeverityStateChanged... throttleSeverityEvents) {
List<StatsLog.EventMetricData> result = new ArrayList<>();
for (AtomsProto.ThermalThrottlingSeverityStateChanged event : throttleSeverityEvents) {
AtomsProto.Atom atom = new AtomsProto.Atom();
atom.setThermalThrottlingSeverityStateChanged(event);
StatsLog.EventMetricData metricData = new StatsLog.EventMetricData();
metricData.atom = atom;
result.add(metricData);
}
return result;
}
/** Returns a state change protobuf for thermal throttling severity. */
private AtomsProto.ThermalThrottlingSeverityStateChanged
getThermalThrottlingSeverityStateChangedEvent(int type, String name, int severity) {
AtomsProto.ThermalThrottlingSeverityStateChanged stateChanged =
new AtomsProto.ThermalThrottlingSeverityStateChanged();
stateChanged.sensorType = type;
stateChanged.sensorName = name;
stateChanged.severity = severity;
return stateChanged;
}
/** Get the thermal metric key for a thermal sensor type and name. */
private String getMetricKey(int type, String name) {
return MetricUtility.constructKey(
"thermal", ThermalHelper.getShorthandSensorType(type), name);
}
@After
public void tearDown() {
mThermalHelper.stopCollecting();
}
}