summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMartin Pitt <martin.pitt@ubuntu.com>2011-04-15 19:44:47 +0200
committerMartin Pitt <martin.pitt@ubuntu.com>2011-04-26 11:50:36 +0200
commite1f4aa38de442682ce122b5d15c6bbad23615ab5 (patch)
tree38d7b3790008b986cfec29368b751150eac7c013 /src
parenteddcf0ef3d0b8445618e368328d7e110a83b69b3 (diff)
Add integration test suite for Linux
Add src/linux/integration-test: This is a Python unittest based test suite which provides methods for building a sandbox sysfs tree, runs upowerd in it, and checks for correct properties. As it is really hard to provide fake uevents, this currently only works for properties which do not depend on dynamic system changes, i. e. you can currently only check the status after coldplugging. However, this already provides enough possibilities for functionality and regression testing, and exposes some bugs with determining the "OnBattery" property under certain conditions like the ones described in <https://bugs.freedesktop.org/show_bug.cgi?id=24371>. If any of the tests fails, the daemon log will be printed to stderr for easier debugging. With the previous commit that adds "upowerd --test", we can also run the integration tests as non-root. If they are called as root, start upowerd normally on the system bus, otherwise on the session bus with --test.
Diffstat (limited to 'src')
-rwxr-xr-xsrc/linux/integration-test467
1 files changed, 467 insertions, 0 deletions
diff --git a/src/linux/integration-test b/src/linux/integration-test
new file mode 100755
index 0000000..3a9997f
--- /dev/null
+++ b/src/linux/integration-test
@@ -0,0 +1,467 @@
+#!/usr/bin/python
+
+# upower integration test suite
+#
+# Run in built tree to test local built binaries, or from anywhere else to test
+# system installed binaries.
+#
+# Copyright: (C) 2011 Martin Pitt <martin.pitt@ubuntu.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+import os
+import sys
+import tempfile
+import subprocess
+import unittest
+import shutil
+import time
+import glib
+
+from gi.repository import Gio
+
+UP = 'org.freedesktop.UPower'
+
+(UP_DEVICE_STATE_UNKNOWN,
+ UP_DEVICE_STATE_CHARGING,
+ UP_DEVICE_STATE_DISCHARGING,
+ UP_DEVICE_STATE_EMPTY,
+ UP_DEVICE_STATE_FULLY_CHARGED) = (0, 1, 2, 3, 4)
+
+class Tests(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ # run from local build tree if we are in one, otherwise use system instance
+ if os.access (os.path.join('src', 'upowerd'), os.X_OK):
+ daemon_path = os.path.join('src', 'upowerd')
+ print('Testing binaries from local build tree')
+ else:
+ print('Testing installed system binaries')
+ daemon_path = None
+ for l in open('/usr/share/dbus-1/system-services/org.freedesktop.UPower.service'):
+ if l.startswith('Exec='):
+ daemon_path = l.split('=', 1)[1].strip()
+ break
+ assert daemon_path, 'could not determine daemon path from D-BUS .service file'
+
+ # if we are root, test the real thing on the system bus, otherwise
+ # start on the session bus
+ if os.geteuid() == 0:
+ # kill running daemons
+ subprocess.call(['killall', 'upowerd'])
+
+ cls.dbus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
+ cls.daemon_argv = [daemon_path]
+ print('Testing as root on the system bus')
+ else:
+ cls.dbus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
+ cls.daemon_argv = [daemon_path, '--test']
+
+ # use dbus-lauch if possible
+ print('Testing as user on the session bus')
+
+
+ def setUp(self):
+ '''Set up a local sysfs tree and determine paths.
+
+ The sysfs tree is empty initially and needs to be populated with
+ @add_device.
+ '''
+ self.sysfs = tempfile.mkdtemp()
+
+ self.proxy = None
+ self.log = None
+ self.daemon = None
+
+ def tearDown(self):
+ self.stop_daemon()
+ shutil.rmtree(self.sysfs)
+
+ # on failures, print daemon log
+ if not self._resultForDoCleanups.wasSuccessful():
+ with open(self.log.name) as f:
+ sys.stderr.write('\n-------------- daemon log: ----------------\n')
+ sys.stderr.write(f.read())
+ sys.stderr.write('------------------------------\n')
+
+ #
+ # Methods for fake sysfs
+ #
+
+ def add_device(self, subsystem, name, attributes, properties=None):
+ '''Add a new device to the local sysfs tree.
+
+ Return the device path.
+ '''
+ dev_dir = os.path.join(self.sysfs, 'devices', name)
+ if not os.path.isdir(dev_dir):
+ os.makedirs(dev_dir)
+ class_dir = os.path.join(self.sysfs, 'class', subsystem)
+ if not os.path.isdir(class_dir):
+ os.makedirs(class_dir)
+
+ os.symlink(os.path.join('..', '..', 'devices', name), os.path.join(class_dir, name))
+ os.symlink(os.path.join('..', '..', 'class', subsystem), os.path.join(dev_dir, 'subsystem'))
+
+ attributes['uevent'] = self._props_to_str(properties)
+
+ for a, v in attributes.items():
+ self.set_sys_attribute(dev_dir, a, v)
+
+ return dev_dir
+
+ def get_sys_attribute(self, devpath, name):
+ '''Get device attribute from the local sysfs tree.'''
+
+ with open(os.path.join(devpath, name), 'r') as f:
+ return f.read()
+
+ def set_sys_attribute(self, devpath, name, value):
+ '''Set device attribute in the local sysfs tree.'''
+
+ with open(os.path.join(devpath, name), 'w') as f:
+ f.write(value)
+
+ def set_sys_property(self, devpath, name, value):
+ '''Set device udev property in the local sysfs tree.'''
+
+ prop_str = self.get_sys_attribute(devpath, 'uevent')
+ props = {}
+ for l in prop_str.splitlines():
+ (k, v) = l.split('=')
+ props[k] = v.rstrip()
+
+ props[name] = value
+
+ self.set_sys_attribute(devpath, 'uevent', self._props_to_str(props))
+
+ #
+ # Daemon control and D-BUS I/O
+ #
+
+ def start_daemon(self):
+ '''Start daemon and create DBus proxy.
+
+ Do this after adding the devices you want to test with. At the moment
+ this only works with coldplugging, as we do not currently have a way to
+ inject simulated uevents.
+
+ When done, this sets self.proxy a the Gio.DBusProxy for upowerd.
+ '''
+ env = os.environ.copy()
+ env['SYSFS_PATH'] = self.sysfs
+ self.log = tempfile.NamedTemporaryFile()
+ self.daemon = subprocess.Popen(self.daemon_argv,
+ env=env, stdout=self.log, stderr=subprocess.STDOUT)
+
+ # wait until the daemon gets online
+ while True:
+ time.sleep(0.1)
+ try:
+ self.get_dbus_property('DaemonVersion')
+ break
+ except glib.GError:
+ pass
+
+ self.proxy = Gio.DBusProxy.new_sync(self.dbus,
+ Gio.DBusProxyFlags.DO_NOT_AUTO_START, None,
+ UP, '/org/freedesktop/UPower', UP, None)
+
+ self.assertEqual(self.daemon.poll(), None, 'daemon crashed')
+
+ def stop_daemon(self):
+ '''Stop the daemon if it is running.'''
+
+ if self.daemon:
+ try:
+ self.daemon.kill()
+ except OSError:
+ pass
+ self.daemon.wait()
+ self.daemon = None
+ self.proxy = None
+
+ def get_dbus_property(self, name):
+ '''Get property value from daemon D-Bus interface.'''
+
+ proxy = Gio.DBusProxy.new_sync(self.dbus,
+ Gio.DBusProxyFlags.DO_NOT_AUTO_START, None,
+ UP, '/org/freedesktop/UPower',
+ 'org.freedesktop.DBus.Properties', None)
+ return proxy.Get('(ss)', UP, name)
+
+ def get_dbus_dev_property(self, device, name):
+ '''Get property value from an upower device D-Bus path.'''
+
+ proxy = Gio.DBusProxy.new_sync(self.dbus,
+ Gio.DBusProxyFlags.DO_NOT_AUTO_START, None,
+ UP, device, 'org.freedesktop.DBus.Properties', None)
+ return proxy.Get('(ss)', UP + '.Device', name)
+
+ #
+ # Actual test cases
+ #
+
+ def test_daemon_version(self):
+ '''DaemonVersion property'''
+
+ self.start_daemon()
+ self.assertEqual(self.proxy.EnumerateDevices(), [])
+ self.assertRegexpMatches(self.get_dbus_property('DaemonVersion'), '^[0-9.]+$')
+
+ # without any devices we should assume AC
+ self.assertEqual(self.get_dbus_property('OnBattery'), False)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+
+ def test_battery_ac(self):
+ '''battery properties with and without AC'''
+
+ # without any devices we should assume AC
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property('OnBattery'), False)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+ self.stop_daemon()
+
+ # online AC
+ ac = self.add_device('power_supply', 'AC',
+ {'type': 'Mains', 'online': '1' })
+
+ self.start_daemon()
+ devs = self.proxy.EnumerateDevices()
+ self.assertEqual(len(devs), 1)
+ ac_up = devs[0]
+ self.assertTrue('line_power_AC' in ac_up)
+ self.assertEqual(self.get_dbus_property('OnBattery'), False)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+ self.assertEqual(self.get_dbus_dev_property(ac_up, 'PowerSupply'), True)
+ self.assertEqual(self.get_dbus_dev_property(ac_up, 'Online'), True)
+ self.assertEqual(self.get_dbus_dev_property(ac_up, 'NativePath'), ac)
+ self.stop_daemon()
+
+ # offline AC
+ self.set_sys_attribute(ac, 'online', '0')
+ self.start_daemon()
+ devs = self.proxy.EnumerateDevices()
+ self.assertEqual(len(devs), 1)
+ # we don't have any known online power device now, but still no battery
+ self.assertEqual(self.get_dbus_property('OnBattery'), False)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+ self.assertEqual(self.get_dbus_dev_property(ac_up, 'Online'), False)
+ self.stop_daemon()
+
+ # offline AC + discharging battery
+ bat0 = self.add_device('power_supply', 'BAT0',
+ {'type': 'Battery',
+ 'present': '1',
+ 'status': 'Discharging',
+ 'energy_full': '60000000',
+ 'energy_full_design': '80000000',
+ 'energy_now': '48000000',
+ 'voltage_now': '12000000'})
+
+ self.start_daemon()
+ devs = self.proxy.EnumerateDevices()
+ self.assertEqual(len(devs), 2)
+ if devs[0] == ac_up:
+ bat0_up = devs[1]
+ else:
+ bat0_up = devs[0]
+ # we don't have any known online power device now, but still no battery
+ self.assertEqual(self.get_dbus_property('OnBattery'), True)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+ self.assertEqual(self.get_dbus_dev_property(bat0_up, 'IsPresent'), True)
+ self.assertEqual(self.get_dbus_dev_property(bat0_up, 'State'), UP_DEVICE_STATE_DISCHARGING)
+ self.assertEqual(self.get_dbus_dev_property(bat0_up, 'Percentage'), 80.0)
+ self.assertEqual(self.get_dbus_dev_property(bat0_up, 'Voltage'), 12.0)
+ self.assertEqual(self.get_dbus_dev_property(bat0_up, 'NativePath'), bat0)
+ self.stop_daemon()
+
+ # offline AC + discharging low battery
+ self.set_sys_attribute(bat0, 'energy_now', '1500000')
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property('OnBattery'), True)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), True)
+ self.assertEqual(self.get_dbus_dev_property(bat0_up, 'IsPresent'), True)
+ self.assertEqual(self.get_dbus_dev_property(bat0_up, 'State'), UP_DEVICE_STATE_DISCHARGING)
+ self.assertEqual(self.get_dbus_dev_property(bat0_up, 'Percentage'), 2.5)
+ self.stop_daemon()
+
+ # now connect AC again
+ self.set_sys_attribute(ac, 'online', '1')
+ self.start_daemon()
+ devs = self.proxy.EnumerateDevices()
+ self.assertEqual(len(devs), 2)
+ # we don't have any known online power device now, but still no battery
+ self.assertEqual(self.get_dbus_property('OnBattery'), False)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+ self.assertEqual(self.get_dbus_dev_property(ac_up, 'Online'), True)
+ self.stop_daemon()
+
+ def test_multiple_batteries(self):
+ '''Multiple batteries'''
+
+ # one well charged, one low
+ bat0 = self.add_device('power_supply', 'BAT0',
+ {'type': 'Battery',
+ 'present': '1',
+ 'status': 'Discharging',
+ 'energy_full': '60000000',
+ 'energy_full_design': '80000000',
+ 'energy_now': '48000000',
+ 'voltage_now': '12000000'})
+
+ bat1 = self.add_device('power_supply', 'BAT1',
+ {'type': 'Battery',
+ 'present': '1',
+ 'status': 'Discharging',
+ 'energy_full': '60000000',
+ 'energy_full_design': '80000000',
+ 'energy_now': '1500000',
+ 'voltage_now': '12000000'})
+
+ self.start_daemon()
+ devs = self.proxy.EnumerateDevices()
+ self.assertEqual(len(devs), 2)
+
+ # as we have one which is well-charged, the summary state is "not low
+ # battery"
+ self.assertEqual(self.get_dbus_property('OnBattery'), True)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+ self.stop_daemon()
+
+ # now set both to low
+ self.set_sys_attribute(bat0, 'energy_now', '1500000')
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property('OnBattery'), True)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), True)
+ self.stop_daemon()
+
+ def test_unknown_battery_status(self):
+ '''Unknown battery charge status'''
+
+ bat0 = self.add_device('power_supply', 'BAT0',
+ {'type': 'Battery',
+ 'present': '1',
+ 'status': 'unknown',
+ 'energy_full': '60000000',
+ 'energy_full_design': '80000000',
+ 'energy_now': '48000000',
+ 'voltage_now': '12000000'})
+
+ # with no other power sources, the OnBattery value here is really
+ # arbitrary, so don't test it. The only thing we know for sure is that
+ # we aren't on low battery
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+ self.stop_daemon()
+
+ # However, if we have an AC, we can infer
+ ac = self.add_device('power_supply', 'AC',
+ {'type': 'Mains', 'online': '0' })
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property('OnBattery'), True)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+ self.stop_daemon()
+
+ self.set_sys_attribute(ac, 'online', '1')
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property('OnBattery'), False)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+ self.stop_daemon()
+
+ def test_ups_ac(self):
+ '''UPS properties with and without AC'''
+
+ # add a charging UPS
+ ups0 = self.add_device('usb', 'hiddev0', {},
+ {'DEVNAME': 'null', 'UPOWER_VENDOR': 'APC',
+ 'UPOWER_BATTERY_TYPE': 'ups',
+ 'UPOWER_FAKE_DEVICE': '1',
+ 'UPOWER_FAKE_HID_CHARGING': '1',
+ 'UPOWER_FAKE_HID_PERCENTAGE': '70'})
+
+ self.start_daemon()
+ devs = self.proxy.EnumerateDevices()
+ self.assertEqual(len(devs), 1)
+ ups0_up = devs[0]
+
+ self.assertEqual(self.get_dbus_dev_property(ups0_up, 'IsPresent'), True)
+ self.assertEqual(self.get_dbus_dev_property(ups0_up, 'Percentage'), 70.0)
+ self.assertEqual(self.get_dbus_dev_property(ups0_up, 'State'), UP_DEVICE_STATE_CHARGING)
+
+ self.assertEqual(self.get_dbus_property('OnBattery'), False)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+ self.stop_daemon()
+
+ # now switch to discharging UPS
+ self.set_sys_property(ups0, 'UPOWER_FAKE_HID_CHARGING', '0')
+
+ self.start_daemon()
+ devs = self.proxy.EnumerateDevices()
+ self.assertEqual(len(devs), 1)
+ self.assertEqual(devs[0], ups0_up)
+
+ self.assertEqual(self.get_dbus_dev_property(ups0_up, 'IsPresent'), True)
+ self.assertEqual(self.get_dbus_dev_property(ups0_up, 'Percentage'), 70.0)
+ self.assertEqual(self.get_dbus_dev_property(ups0_up, 'State'), UP_DEVICE_STATE_DISCHARGING)
+ self.assertEqual(self.get_dbus_property('OnBattery'), True)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+ self.stop_daemon()
+
+ # low UPS charge
+ self.set_sys_property(ups0, 'UPOWER_FAKE_HID_PERCENTAGE', '2')
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_dev_property(ups0_up, 'Percentage'), 2.0)
+ self.assertEqual(self.get_dbus_dev_property(ups0_up, 'State'), UP_DEVICE_STATE_DISCHARGING)
+ self.assertEqual(self.get_dbus_property('OnBattery'), True)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), True)
+ self.stop_daemon()
+
+ # now add an offline AC, should still be on battery
+ ac = self.add_device('power_supply', 'AC',
+ {'type': 'Mains', 'online': '0' })
+ self.start_daemon()
+ devs = self.proxy.EnumerateDevices()
+ self.assertEqual(len(devs), 2)
+ if devs[0] == ups0_up:
+ ac_up = devs[1]
+ else:
+ ac_up = devs[0]
+
+ self.assertEqual(self.get_dbus_dev_property(ups0_up, 'Percentage'), 2.0)
+ self.assertEqual(self.get_dbus_dev_property(ups0_up, 'State'), UP_DEVICE_STATE_DISCHARGING)
+ self.assertEqual(self.get_dbus_property('OnBattery'), True)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), True)
+ self.stop_daemon()
+
+ # now plug in the AC, should switch to OnBattery=False
+ self.set_sys_attribute(ac, 'online', '1')
+ self.start_daemon()
+ self.assertEqual(self.get_dbus_property('OnBattery'), False)
+ self.assertEqual(self.get_dbus_property('OnLowBattery'), False)
+ self.stop_daemon()
+
+ #
+ # Helper methods
+ #
+
+ @classmethod
+ def _props_to_str(cls, properties):
+ '''Convert a properties dictionary to uevent text representation.'''
+
+ prop_str = ''
+ if properties:
+ for k, v in properties.items():
+ prop_str += '%s=%s\n' % (k, v)
+ return prop_str
+
+if __name__ == '__main__':
+ unittest.main()