mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-25 21:01:38 +01:00
87 lines
4.3 KiB
Python
87 lines
4.3 KiB
Python
"""Read-only sysfs battery parsing; fixture values, no real headset access."""
|
|
import sandbox # noqa: F401
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'ui'))
|
|
import frame_status as status
|
|
|
|
|
|
class Battery(unittest.TestCase):
|
|
def probe(self, fields):
|
|
# Real files exercise missing files, whitespace and integer conversion.
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
node = Path(tmp) / 'max1720x_bat_7-36'
|
|
node.mkdir()
|
|
for name, value in dict(type='Battery', **fields).items():
|
|
(node / name).write_text(str(value) + '\n')
|
|
with mock.patch.object(status.glob, 'glob', return_value=[str(node)]):
|
|
result = status.battery()
|
|
if result:
|
|
result.pop('source')
|
|
return result
|
|
|
|
def test_frame_units_and_optional_health(self):
|
|
b = self.probe(dict(capacity=73, status='Charging', voltage_now=3800000,
|
|
current_now=1000000, temp=325, health='Good',
|
|
charge_full=4600000, charge_full_design=5000000,
|
|
cycle_count=42, time_to_full_now=3600))
|
|
self.assertEqual(b, dict(percent=73, status='Charging', watts=3.8,
|
|
tempC=32.5, health='Good', timeToFull=3600,
|
|
timeToEmpty=None, cycleCount=42, fullMah=4600,
|
|
designMah=5000, capacityHealthPercent=92))
|
|
|
|
def test_zero_negative_current_and_temperature(self):
|
|
b = self.probe(dict(capacity=0, cycle_count=0, temp=-50,
|
|
voltage_now=3800000, current_now=-1000000))
|
|
self.assertEqual((b['percent'], b['cycleCount'], b['tempC'], b['watts']),
|
|
(0, 0, -5, -3.8))
|
|
|
|
def test_missing_optional_sensors_stay_null(self):
|
|
b = self.probe(dict(capacity=50, status='Discharging'))
|
|
self.assertEqual(b['percent'], 50)
|
|
self.assertTrue(all(value is None for key, value in b.items()
|
|
if key not in ('percent', 'status')))
|
|
|
|
def test_invalid_values_do_not_break_other_readings(self):
|
|
for invalid in ('abc', '', '-1', '101', '12.5'):
|
|
b = self.probe(dict(capacity=invalid, status='Charging', temp='bad',
|
|
charge_full=4500000, charge_full_design=0,
|
|
time_to_full_now=-1, time_to_empty_now=4294967295,
|
|
cycle_count=-1, voltage_now='bad', current_now=100))
|
|
self.assertIsNone(b['percent'])
|
|
for key in ('watts', 'tempC', 'capacityHealthPercent', 'timeToFull', 'timeToEmpty', 'cycleCount'):
|
|
self.assertIsNone(b[key], key)
|
|
self.assertEqual(b['status'], 'Charging')
|
|
|
|
def test_no_battery_and_unreadable_type(self):
|
|
with mock.patch.object(status.glob, 'glob', return_value=[]):
|
|
self.assertIsNone(status.battery())
|
|
with mock.patch.object(status.glob, 'glob', return_value=['/missing']), \
|
|
mock.patch('builtins.open', side_effect=PermissionError):
|
|
self.assertIsNone(status.battery())
|
|
|
|
def test_prefers_frame_and_excludes_peripheral_or_absent_batteries(self):
|
|
paths = ['/sys/class/power_supply/aaa', '/sys/class/power_supply/max1720x_bat_7-36']
|
|
def read(path):
|
|
return {'type': 'Battery', 'capacity': '80'}.get(path.rsplit('/', 1)[-1])
|
|
with mock.patch.object(status.glob, 'glob', return_value=paths), \
|
|
mock.patch.object(status, 'read', side_effect=read):
|
|
self.assertEqual(status.battery()['source'], paths[1])
|
|
self.assertIsNone(self.probe(dict(scope='Device', capacity=99)))
|
|
self.assertIsNone(self.probe(dict(present=0, capacity=99)))
|
|
|
|
def test_permission_failure_affects_only_one_sensor(self):
|
|
actual_open = open
|
|
def guarded(path, *args, **kwargs):
|
|
if str(path).endswith('/temp'):
|
|
raise PermissionError('unreadable')
|
|
return actual_open(path, *args, **kwargs)
|
|
with mock.patch('builtins.open', side_effect=guarded):
|
|
b = self.probe(dict(capacity=45, temp=300))
|
|
self.assertEqual(b['percent'], 45)
|
|
self.assertIsNone(b['tempC'])
|