#!/usr/bin/env python # Copyright 2014 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """ Unit tests for the contents of battery_utils.py """ # pylint: disable=W0613 import logging import os import sys import unittest from pylib import constants from pylib.device import battery_utils from pylib.device import device_errors from pylib.device import device_utils from pylib.device import device_utils_test from pylib.utils import mock_calls # RunCommand from third_party/android_testrunner/run_command.py is mocked # below, so its path needs to be in sys.path. sys.path.append(os.path.join( constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner')) sys.path.append(os.path.join( constants.DIR_SOURCE_ROOT, 'third_party', 'pymock')) import mock # pylint: disable=F0401 _DUMPSYS_OUTPUT = [ '9,0,i,uid,1000,test_package1', '9,0,i,uid,1001,test_package2', '9,1000,l,pwi,uid,1', '9,1001,l,pwi,uid,2' ] class BatteryUtilsTest(mock_calls.TestCase): _NEXUS_5 = { 'name': 'Nexus 5', 'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT', 'enable_command': ( 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' 'echo 1 > /sys/class/power_supply/usb/online'), 'disable_command': ( 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' 'chmod 644 /sys/class/power_supply/usb/online && ' 'echo 0 > /sys/class/power_supply/usb/online'), 'charge_counter': None, 'voltage': None, 'current': None, } _NEXUS_6 = { 'name': 'Nexus 6', 'witness_file': None, 'enable_command': None, 'disable_command': None, 'charge_counter': ( '/sys/class/power_supply/max170xx_battery/charge_counter_ext'), 'voltage': '/sys/class/power_supply/max170xx_battery/voltage_now', 'current': '/sys/class/power_supply/max170xx_battery/current_now', } _NEXUS_10 = { 'name': 'Nexus 10', 'witness_file': None, 'enable_command': None, 'disable_command': None, 'charge_counter': ( '/sys/class/power_supply/ds2784-fuelgauge/charge_counter_ext'), 'voltage': '/sys/class/power_supply/ds2784-fuelgauge/voltage_now', 'current': '/sys/class/power_supply/ds2784-fuelgauge/current_now', } def ShellError(self, output=None, status=1): def action(cmd, *args, **kwargs): raise device_errors.AdbShellCommandFailedError( cmd, output, status, str(self.device)) if output is None: output = 'Permission denied\n' return action def setUp(self): self.adb = device_utils_test._AdbWrapperMock('0123456789abcdef') self.device = device_utils.DeviceUtils( self.adb, default_timeout=10, default_retries=0) self.watchMethodCalls(self.call.adb, ignore=['GetDeviceSerial']) self.battery = battery_utils.BatteryUtils( self.device, default_timeout=10, default_retries=0) class BatteryUtilsInitTest(unittest.TestCase): def testInitWithDeviceUtil(self): serial = '0fedcba987654321' d = device_utils.DeviceUtils(serial) b = battery_utils.BatteryUtils(d) self.assertEqual(d, b._device) def testInitWithMissing_fails(self): with self.assertRaises(TypeError): battery_utils.BatteryUtils(None) with self.assertRaises(TypeError): battery_utils.BatteryUtils('') class BatteryUtilsSetChargingTest(BatteryUtilsTest): @mock.patch('time.sleep', mock.Mock()) def testSetCharging_enabled(self): self.battery._cache['profile'] = self._NEXUS_5 with self.assertCalls( (self.call.device.RunShellCommand(mock.ANY, check_return=True), []), (self.call.battery.GetCharging(), False), (self.call.device.RunShellCommand(mock.ANY, check_return=True), []), (self.call.battery.GetCharging(), True)): self.battery.SetCharging(True) def testSetCharging_alreadyEnabled(self): self.battery._cache['profile'] = self._NEXUS_5 with self.assertCalls( (self.call.device.RunShellCommand(mock.ANY, check_return=True), []), (self.call.battery.GetCharging(), True)): self.battery.SetCharging(True) @mock.patch('time.sleep', mock.Mock()) def testSetCharging_disabled(self): self.battery._cache['profile'] = self._NEXUS_5 with self.assertCalls( (self.call.device.RunShellCommand(mock.ANY, check_return=True), []), (self.call.battery.GetCharging(), True), (self.call.device.RunShellCommand(mock.ANY, check_return=True), []), (self.call.battery.GetCharging(), False)): self.battery.SetCharging(False) class BatteryUtilsSetBatteryMeasurementTest(BatteryUtilsTest): @mock.patch('time.sleep', mock.Mock()) def testBatteryMeasurementWifi(self): with self.assertCalls( (self.call.device.RunShellCommand( mock.ANY, retries=0, single_line=True, timeout=10, check_return=True), '22'), (self.call.battery._ClearPowerData(), True), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'set', 'ac', '0'], check_return=True), []), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'set', 'usb', '0'], check_return=True), []), (self.call.battery.GetCharging(), False), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'reset'], check_return=True), []), (self.call.battery.GetCharging(), False), (self.call.device.RunShellCommand( ['dumpsys', 'battery'], check_return=True), ['UPDATES STOPPED']), (self.call.battery.GetCharging(), False), (self.call.device.RunShellCommand( ['dumpsys', 'battery'], check_return=True), [])): with self.battery.BatteryMeasurement(): pass @mock.patch('time.sleep', mock.Mock()) def testBatteryMeasurementUsb(self): with self.assertCalls( (self.call.device.RunShellCommand( mock.ANY, retries=0, single_line=True, timeout=10, check_return=True), '22'), (self.call.battery._ClearPowerData(), True), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'set', 'ac', '0'], check_return=True), []), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'set', 'usb', '0'], check_return=True), []), (self.call.battery.GetCharging(), False), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'reset'], check_return=True), []), (self.call.battery.GetCharging(), False), (self.call.device.RunShellCommand( ['dumpsys', 'battery'], check_return=True), ['UPDATES STOPPED']), (self.call.battery.GetCharging(), True)): with self.battery.BatteryMeasurement(): pass class BatteryUtilsGetPowerData(BatteryUtilsTest): def testGetPowerData(self): with self.assertCalls( (self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '-c'], check_return=True), _DUMPSYS_OUTPUT)): data = self.battery.GetPowerData() check = { 'test_package1': {'uid': '1000', 'data': [1.0]}, 'test_package2': {'uid': '1001', 'data': [2.0]} } self.assertEqual(data, check) def testGetPowerData_packageCollisionSame(self): self.battery._cache['uids'] = {'test_package1': '1000'} with self.assertCall( self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '-c'], check_return=True), _DUMPSYS_OUTPUT): data = self.battery.GetPowerData() check = { 'test_package1': {'uid': '1000', 'data': [1.0]}, 'test_package2': {'uid': '1001', 'data': [2.0]} } self.assertEqual(data, check) def testGetPowerData_packageCollisionDifferent(self): self.battery._cache['uids'] = {'test_package1': '1'} with self.assertCall( self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '-c'], check_return=True), _DUMPSYS_OUTPUT): with self.assertRaises(device_errors.CommandFailedError): self.battery.GetPowerData() def testGetPowerData_cacheCleared(self): with self.assertCalls( (self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '-c'], check_return=True), _DUMPSYS_OUTPUT)): self.battery._cache.clear() data = self.battery.GetPowerData() check = { 'test_package1': {'uid': '1000', 'data': [1.0]}, 'test_package2': {'uid': '1001', 'data': [2.0]} } self.assertEqual(data, check) def testGetPackagePowerData(self): with self.assertCalls( (self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '-c'], check_return=True), _DUMPSYS_OUTPUT)): data = self.battery.GetPackagePowerData('test_package2') self.assertEqual(data, {'uid': '1001', 'data': [2.0]}) def testGetPackagePowerData_badPackage(self): with self.assertCalls( (self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '-c'], check_return=True), _DUMPSYS_OUTPUT)): data = self.battery.GetPackagePowerData('not_a_package') self.assertEqual(data, None) class BatteryUtilsChargeDevice(BatteryUtilsTest): @mock.patch('time.sleep', mock.Mock()) def testChargeDeviceToLevel(self): with self.assertCalls( (self.call.battery.SetCharging(True)), (self.call.battery.GetBatteryInfo(), {'level': '50'}), (self.call.battery.GetBatteryInfo(), {'level': '100'})): self.battery.ChargeDeviceToLevel(95) class BatteryUtilsGetBatteryInfoTest(BatteryUtilsTest): def testGetBatteryInfo_normal(self): with self.assertCall( self.call.device.RunShellCommand( ['dumpsys', 'battery'], check_return=True), [ 'Current Battery Service state:', ' AC powered: false', ' USB powered: true', ' level: 100', ' temperature: 321', ]): self.assertEquals( { 'AC powered': 'false', 'USB powered': 'true', 'level': '100', 'temperature': '321', }, self.battery.GetBatteryInfo()) def testGetBatteryInfo_nothing(self): with self.assertCall( self.call.device.RunShellCommand( ['dumpsys', 'battery'], check_return=True), []): self.assertEquals({}, self.battery.GetBatteryInfo()) class BatteryUtilsGetChargingTest(BatteryUtilsTest): def testGetCharging_usb(self): with self.assertCall( self.call.battery.GetBatteryInfo(), {'USB powered': 'true'}): self.assertTrue(self.battery.GetCharging()) def testGetCharging_usbFalse(self): with self.assertCall( self.call.battery.GetBatteryInfo(), {'USB powered': 'false'}): self.assertFalse(self.battery.GetCharging()) def testGetCharging_ac(self): with self.assertCall( self.call.battery.GetBatteryInfo(), {'AC powered': 'true'}): self.assertTrue(self.battery.GetCharging()) def testGetCharging_wireless(self): with self.assertCall( self.call.battery.GetBatteryInfo(), {'Wireless powered': 'true'}): self.assertTrue(self.battery.GetCharging()) def testGetCharging_unknown(self): with self.assertCall( self.call.battery.GetBatteryInfo(), {'level': '42'}): self.assertFalse(self.battery.GetCharging()) class BatteryUtilsGetNetworkDataTest(BatteryUtilsTest): def testGetNetworkData_noDataUsage(self): with self.assertCalls( (self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '-c'], check_return=True), _DUMPSYS_OUTPUT), (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_snd'), self.ShellError()), (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_rcv'), self.ShellError())): self.assertEquals(self.battery.GetNetworkData('test_package1'), (0, 0)) def testGetNetworkData_badPackage(self): with self.assertCall( self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '-c'], check_return=True), _DUMPSYS_OUTPUT): self.assertEqual(self.battery.GetNetworkData('asdf'), None) def testGetNetworkData_packageNotCached(self): with self.assertCalls( (self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '-c'], check_return=True), _DUMPSYS_OUTPUT), (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_snd'), 1), (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_rcv'), 2)): self.assertEqual(self.battery.GetNetworkData('test_package1'), (1,2)) def testGetNetworkData_packageCached(self): self.battery._cache['uids'] = {'test_package1': '1000'} with self.assertCalls( (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_snd'), 1), (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_rcv'), 2)): self.assertEqual(self.battery.GetNetworkData('test_package1'), (1,2)) def testGetNetworkData_clearedCache(self): with self.assertCalls( (self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '-c'], check_return=True), _DUMPSYS_OUTPUT), (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_snd'), 1), (self.call.device.ReadFile('/proc/uid_stat/1000/tcp_rcv'), 2)): self.battery._cache.clear() self.assertEqual(self.battery.GetNetworkData('test_package1'), (1,2)) class BatteryUtilsLetBatteryCoolToTemperatureTest(BatteryUtilsTest): @mock.patch('time.sleep', mock.Mock()) def testLetBatteryCoolToTemperature_startUnder(self): with self.assertCalls( (self.call.battery.EnableBatteryUpdates(), []), (self.call.battery.GetBatteryInfo(), {'temperature': '500'})): self.battery.LetBatteryCoolToTemperature(600) @mock.patch('time.sleep', mock.Mock()) def testLetBatteryCoolToTemperature_startOver(self): with self.assertCalls( (self.call.battery.EnableBatteryUpdates(), []), (self.call.battery.GetBatteryInfo(), {'temperature': '500'}), (self.call.battery.GetBatteryInfo(), {'temperature': '400'})): self.battery.LetBatteryCoolToTemperature(400) class BatteryUtilsSupportsFuelGaugeTest(BatteryUtilsTest): def testSupportsFuelGauge_false(self): self.battery._cache['profile'] = self._NEXUS_5 self.assertFalse(self.battery.SupportsFuelGauge()) def testSupportsFuelGauge_trueMax(self): self.battery._cache['profile'] = self._NEXUS_6 # TODO(rnephew): Change this to assertTrue when we have support for # disabling hardware charging on nexus 6. self.assertFalse(self.battery.SupportsFuelGauge()) def testSupportsFuelGauge_trueDS(self): self.battery._cache['profile'] = self._NEXUS_10 # TODO(rnephew): Change this to assertTrue when we have support for # disabling hardware charging on nexus 10. self.assertFalse(self.battery.SupportsFuelGauge()) class BatteryUtilsGetFuelGaugeChargeCounterTest(BatteryUtilsTest): def testGetFuelGaugeChargeCounter_noFuelGauge(self): self.battery._cache['profile'] = self._NEXUS_5 with self.assertRaises(device_errors.CommandFailedError): self.battery.GetFuelGaugeChargeCounter() def testGetFuelGaugeChargeCounter_fuelGaugePresent(self): self.battery._cache['profile']= self._NEXUS_6 with self.assertCalls( (self.call.battery.SupportsFuelGauge(), True), (self.call.device.ReadFile(mock.ANY), '123')): self.assertEqual(self.battery.GetFuelGaugeChargeCounter(), 123) class BatteryUtilsTieredSetCharging(BatteryUtilsTest): @mock.patch('time.sleep', mock.Mock()) def testTieredSetCharging_softwareSetTrue(self): self.battery._cache['profile'] = self._NEXUS_6 with self.assertCalls( (self.call.battery.GetCharging(), False), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'reset'], check_return=True), []), (self.call.battery.GetCharging(), False), (self.call.device.RunShellCommand( ['dumpsys', 'battery'], check_return=True), ['UPDATES STOPPED']), (self.call.battery.GetCharging(), True)): self.battery.TieredSetCharging(True) @mock.patch('time.sleep', mock.Mock()) def testTieredSetCharging_softwareSetFalse(self): self.battery._cache['profile'] = self._NEXUS_6 with self.assertCalls( (self.call.battery.GetCharging(), True), (self.call.battery._ClearPowerData(), True), (self.call.battery._ClearPowerData(), True), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'set', 'ac', '0'], check_return=True), []), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'set', 'usb', '0'], check_return=True), []), (self.call.battery.GetCharging(), False)): self.battery.TieredSetCharging(False) @mock.patch('time.sleep', mock.Mock()) def testTieredSetCharging_hardwareSetTrue(self): self.battery._cache['profile'] = self._NEXUS_5 with self.assertCalls( (self.call.battery.GetCharging(), False), (self.call.battery.SetCharging(True))): self.battery.TieredSetCharging(True) @mock.patch('time.sleep', mock.Mock()) def testTieredSetCharging_hardwareSetFalse(self): self.battery._cache['profile'] = self._NEXUS_5 with self.assertCalls( (self.call.battery.GetCharging(), True), (self.call.battery._ClearPowerData(), True), (self.call.battery.SetCharging(False))): self.battery.TieredSetCharging(False) def testTieredSetCharging_expectedStateAlreadyTrue(self): with self.assertCalls((self.call.battery.GetCharging(), True)): self.battery.TieredSetCharging(True) def testTieredSetCharging_expectedStateAlreadyFalse(self): with self.assertCalls((self.call.battery.GetCharging(), False)): self.battery.TieredSetCharging(False) class BatteryUtilsPowerMeasurement(BatteryUtilsTest): def testPowerMeasurement_hardware(self): self.battery._cache['profile'] = self._NEXUS_5 with self.assertCalls( (self.call.battery.GetCharging(), True), (self.call.battery._ClearPowerData(), True), (self.call.battery.SetCharging(False)), (self.call.battery.GetCharging(), False), (self.call.battery.SetCharging(True))): with self.battery.PowerMeasurement(): pass @mock.patch('time.sleep', mock.Mock()) def testPowerMeasurement_software(self): self.battery._cache['profile'] = self._NEXUS_6 with self.assertCalls( (self.call.battery.GetCharging(), True), (self.call.battery._ClearPowerData(), True), (self.call.battery._ClearPowerData(), True), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'set', 'ac', '0'], check_return=True), []), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'set', 'usb', '0'], check_return=True), []), (self.call.battery.GetCharging(), False), (self.call.battery.GetCharging(), False), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'reset'], check_return=True), []), (self.call.battery.GetCharging(), False), (self.call.device.RunShellCommand( ['dumpsys', 'battery'], check_return=True), ['UPDATES STOPPED']), (self.call.battery.GetCharging(), True)): with self.battery.PowerMeasurement(): pass class BatteryUtilsDiscoverDeviceProfile(BatteryUtilsTest): def testDiscoverDeviceProfile_known(self): with self.assertCalls( (self.call.adb.Shell('getprop ro.product.model'), "Nexus 4")): self.battery._DiscoverDeviceProfile() self.assertEqual(self.battery._cache['profile']['name'], "Nexus 4") def testDiscoverDeviceProfile_unknown(self): with self.assertCalls( (self.call.adb.Shell('getprop ro.product.model'), "Other")): self.battery._DiscoverDeviceProfile() self.assertEqual(self.battery._cache['profile']['name'], None) class BatteryUtilsClearPowerData(BatteryUtilsTest): def testClearPowerData_preL(self): with self.assertCalls( (self.call.device.RunShellCommand(mock.ANY, retries=0, single_line=True, timeout=10, check_return=True), '20')): self.assertFalse(self.battery._ClearPowerData()) def testClearPowerData_clearedL(self): with self.assertCalls( (self.call.device.RunShellCommand(mock.ANY, retries=0, single_line=True, timeout=10, check_return=True), '22'), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'set', 'usb', '1'], check_return=True), []), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'set', 'ac', '1'], check_return=True), []), (self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '--reset'], check_return=True), []), (self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '--charged', '--checkin'], check_return=True, large_output=True), []), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'reset'], check_return=True), [])): self.assertTrue(self.battery._ClearPowerData()) def testClearPowerData_notClearedL(self): with self.assertCalls( (self.call.device.RunShellCommand(mock.ANY, retries=0, single_line=True, timeout=10, check_return=True), '22'), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'set', 'usb', '1'], check_return=True), []), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'set', 'ac', '1'], check_return=True), []), (self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '--reset'], check_return=True), []), (self.call.device.RunShellCommand( ['dumpsys', 'batterystats', '--charged', '--checkin'], check_return=True, large_output=True), ['9,1000,l,pwi,uid,0.0327']), (self.call.device.RunShellCommand( ['dumpsys', 'battery', 'reset'], check_return=True), [])): with self.assertRaises(device_errors.CommandFailedError): self.battery._ClearPowerData() if __name__ == '__main__': logging.getLogger().setLevel(logging.DEBUG) unittest.main(verbosity=2)