diff --git a/src/umockdev.vala b/src/umockdev.vala index f700716..e41d539 100644 --- a/src/umockdev.vala +++ b/src/umockdev.vala @@ -637,13 +637,57 @@ public class Testbed: GLib.Object { public void remove_device (string syspath) { string real_path = Path.build_filename(this.root_dir, syspath); - string devname = Path.get_basename(syspath); if (!FileUtils.test(real_path, FileTest.IS_DIR)) { critical("umockdev_testbed_remove_device(): device %s does not exist", syspath); return; } + string path = Path.build_filename(real_path, "uevent"); + if (!FileUtils.test(path, FileTest.IS_REGULAR)) { + critical("umockdev_testbed_remove_device(): device %s does not appear to be a device", syspath); + return; + } + + remove_with_children(syspath); + } + + private void remove_with_children (string syspath) + { + string real_path = Path.build_filename(this.root_dir, syspath); + + try { + Dir dir = Dir.open(real_path); + string? name = null; + while (( name = dir.read_name( )) != null) { + string path = Path.build_filename(real_path, name); + // Skip over symlinks + if (FileUtils.test(path, FileTest.IS_SYMLINK)) { + continue; + } + + // Recurse into the directory and remove any children therein. + if (FileUtils.test(path, FileTest.IS_DIR)) { + string child_syspath = Path.build_filename(syspath, name); + remove_with_children(child_syspath); + } + } + } catch (FileError e) { + critical("umockdev_testbed_remove_device(): cannot determine children of %s: %s", + syspath, e.message); + } + + // See if this syspath actually corresponds to a device by seeing if + // there is a "uevent" file in the directory. If not, then there is + // no device to remove so the work is finished. + string path = Path.build_filename(real_path, "uevent"); + if (!FileUtils.test(path, FileTest.IS_REGULAR)) { + return; + } + + // This syspath corresponds to a device, so remove it. + + string devname = Path.get_basename(syspath); string subsystem; try { diff --git a/tests/test-umockdev.py b/tests/test-umockdev.py index a08c120..e7b9f8a 100644 --- a/tests/test-umockdev.py +++ b/tests/test-umockdev.py @@ -192,6 +192,59 @@ def on_uevent(client, action, device, counters): mainloop.run() self.assertEqual(counter, [0, 1, 0, syspath, None, '1']) + def test_remove_usb_parent_device(self): + '''testbed removing a USB parent device removes children''' + + counter = {} + + def on_uevent(client, action, device, counters): + key = device.get_sysfs_path() + counters.setdefault(key, [0, 0]) + if action == 'add': + counters[key][0] += 1 + elif action == 'remove': + counters[key][1] += 1 + + # set up listener for uevent signal + client = GUdev.Client.new(['usb']) + client.connect('uevent', on_uevent, counter) + mainloop = GLib.MainLoop() + + parent_syspath = self.testbed.add_device('usb', 'myparentdev', None, ['idVendor', '0815'], ['ID_INPUT', '1']) + self.assertNotEqual(parent_syspath, None) + + child1_syspath = self.testbed.add_device('usb', 'child1', parent_syspath, ['idVendor', '0815'], ['ID_INPUT', '1']) + self.assertNotEqual(child1_syspath, None) + + child2_syspath = self.testbed.add_device('usb', 'child2', parent_syspath, ['idVendor', '0815'], ['ID_INPUT', '1']) + self.assertNotEqual(child2_syspath, None) + + # Run the main loop for 0.5 seconds to catch the "add" uevent, which + # should be automatically generated by the call to `add_device()`. + GLib.timeout_add(500, mainloop.quit) + mainloop.run() + self.assertIn(parent_syspath, counter) + self.assertIn(child1_syspath, counter) + self.assertIn(child2_syspath, counter) + self.assertEqual(counter[parent_syspath], [1, 0]) + self.assertEqual(counter[child1_syspath], [1, 0]) + self.assertEqual(counter[child2_syspath], [1, 0]) + + # Just remove the parent, the children should also be removed. + self.testbed.remove_device(parent_syspath) + + # Run the main loop for 0.5 seconds to catch the "remove" uevent, which + # should be automatically generated by the call to `remove_device()`. + GLib.timeout_add(500, mainloop.quit) + mainloop.run() + self.assertEqual(counter[parent_syspath], [1, 1]) + self.assertEqual(counter[child1_syspath], [1, 1]) + self.assertEqual(counter[child2_syspath], [1, 1]) + + # Verify that the parent syspath has been removed (and therefore the + # children syspaths have also been removed.) + self.assertFalse(os.path.exists(parent_syspath)) + def test_add_from_string(self): self.assertTrue(self.testbed.add_from_string('''P: /devices/dev1 E: SIMPLE_PROP=1