Browse Source

tests: add tests for qvm-template remove

WillyPillow 3 years ago
parent
commit
ed3e368673
2 changed files with 207 additions and 2 deletions
  1. 203 1
      qubesadmin/tests/tools/qvm_template.py
  2. 4 1
      qubesadmin/tools/qvm_template.py

+ 203 - 1
qubesadmin/tests/tools/qvm_template.py

@@ -3891,4 +3891,206 @@ test-vm : Qubes template for fedora-31
         self.assertEqual(mock_mkdirs.mock_calls, [
             mock.call(args.cachedir, exist_ok=True)
         ])
-        self.assertAllCalled()
+        self.assertAllCalled()
+
+    @mock.patch('qubesadmin.tools.qvm_remove.main')
+    @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
+    def test_210_remove_success(self, mock_confirm, mock_remove):
+        self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
+            b'0\x00vm1 class=TemplateVM state=Halted\n'
+            b'vm2 class=TemplateVM state=Halted\n'
+        )
+        args = argparse.Namespace(
+            templates=['vm1', 'vm2'],
+            yes=False
+        )
+        qubesadmin.tools.qvm_template.remove(args, self.app)
+        self.assertEqual(mock_confirm.mock_calls,
+            [mock.call(re_str(r'.*completely remove.*'), ['vm1', 'vm2'])])
+        self.assertEqual(mock_remove.mock_calls, [
+            mock.call(['--force', '--', 'vm1', 'vm2'], self.app)
+        ])
+        self.assertAllCalled()
+
+    @mock.patch('qubesadmin.tools.qvm_kill.main')
+    @mock.patch('qubesadmin.tools.qvm_remove.main')
+    @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
+    def test_211_remove_purge_disassoc_success(
+            self,
+            mock_confirm,
+            mock_remove,
+            mock_kill):
+        self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
+            b'0\x00vm1 class=TemplateVM state=Halted\n'
+            b'vm2 class=TemplateVM state=Halted\n'
+            b'vm3 class=TemplateVM state=Halted\n'
+            b'vm4 class=TemplateVM state=Halted\n'
+            b'dummy class=TemplateVM state=Halted\n'
+            b'dummy-1 class=TemplateVM state=Halted\n'
+        )
+        self.app.expected_calls[
+                ('dummy', 'admin.vm.feature.Get', 'template-dummy', None)] = \
+            b'0\x000'
+        self.app.expected_calls[
+                ('dummy-1', 'admin.vm.feature.Get',
+                    'template-dummy', None)] = \
+            b'0\x001'
+        self.app.expected_calls[
+                ('vm2', 'admin.vm.property.Set',
+                    'default_template', b'dummy-1')] = \
+            b'0\x00'
+        self.app.expected_calls[
+                ('vm2', 'admin.vm.property.Set', 'template', b'dummy-1')] = \
+            b'0\x00'
+        self.app.expected_calls[
+                ('vm3', 'admin.vm.property.Set', 'netvm', b'dummy-1')] = \
+            b'0\x00'
+        self.app.expected_calls[
+                ('vm3', 'admin.vm.property.Set', 'template', b'dummy-1')] = \
+            b'0\x00'
+        self.app.expected_calls[
+                ('vm4', 'admin.vm.property.Set', 'netvm', b'dummy-1')] = \
+            b'0\x00'
+        self.app.expected_calls[
+                ('vm4', 'admin.vm.property.Set', 'template', b'dummy-1')] = \
+            b'0\x00'
+        self.app.expected_calls[
+                ('dom0', 'admin.property.Set', 'updatevm', b'')] = \
+            b'0\x00'
+        args = argparse.Namespace(
+            templates=['vm1'],
+            yes=False
+        )
+        def deps(app, vm):
+            if vm == 'vm1':
+                return [(self.app.domains['vm2'], 'default_template'),
+                        (self.app.domains['vm3'], 'netvm')]
+            if vm == 'vm2' or vm == 'vm3':
+                return [(self.app.domains['vm4'], 'netvm')]
+            if vm == 'vm4':
+                return [(None, 'updatevm')]
+            return []
+        with mock.patch('qubesadmin.utils.vm_dependencies') as mock_deps:
+            mock_deps.side_effect = deps
+            qubesadmin.tools.qvm_template.remove(args, self.app, purge=True)
+            # Once for purge (dependency detection) and
+            # one for disassoc (actually disassociating the dependencies
+            self.assertEqual(mock_deps.mock_calls, [
+                mock.call(self.app, self.app.domains['vm1']),
+                mock.call(self.app, self.app.domains['vm2']),
+                mock.call(self.app, self.app.domains['vm3']),
+                mock.call(self.app, self.app.domains['vm4']),
+                mock.call(self.app, self.app.domains['vm1']),
+                mock.call(self.app, self.app.domains['vm2']),
+                mock.call(self.app, self.app.domains['vm3']),
+                mock.call(self.app, self.app.domains['vm4'])
+            ])
+        self.assertEqual(mock_confirm.mock_calls, [
+            mock.call(re_str(r'.*completely remove.*'),
+                ['vm1', 'vm2', 'vm3', 'vm4']),
+            mock.call(re_str(r'.*completely remove.*'),
+                ['vm1', 'vm2', 'vm3', 'vm4']),
+            mock.call(re_str(r'.*completely remove.*'),
+                ['vm1', 'vm2', 'vm3', 'vm4'])
+        ])
+        self.assertEqual(mock_remove.mock_calls, [
+            mock.call(['--force', '--', 'vm1', 'vm2', 'vm3', 'vm4', 'dummy-1'],
+                self.app)
+        ])
+        self.assertEqual(mock_kill.mock_calls, [
+            mock.call(['--', 'vm1', 'vm2', 'vm3', 'vm4', 'dummy-1'], self.app)
+        ])
+        self.assertAllCalled()
+
+    @mock.patch('qubesadmin.tools.qvm_kill.main')
+    @mock.patch('qubesadmin.tools.qvm_remove.main')
+    @mock.patch('qubesadmin.tools.qvm_template.confirm_action')
+    def test_212_remove_disassoc_success(
+            self,
+            mock_confirm,
+            mock_remove,
+            mock_kill):
+        self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = (
+            b'0\x00vm1 class=TemplateVM state=Halted\n'
+            b'vm2 class=TemplateVM state=Halted\n'
+            b'vm3 class=TemplateVM state=Halted\n'
+            b'vm4 class=TemplateVM state=Halted\n'
+            b'dummy class=TemplateVM state=Halted\n'
+            b'dummy-1 class=TemplateVM state=Halted\n'
+        )
+        self.app.expected_calls[
+                ('dummy', 'admin.vm.feature.Get', 'template-dummy', None)] = \
+            b'0\x000'
+        self.app.expected_calls[
+                ('dummy-1', 'admin.vm.feature.Get',
+                    'template-dummy', None)] = \
+            b'0\x001'
+        self.app.expected_calls[
+                ('vm2', 'admin.vm.property.Set',
+                    'default_template', b'dummy-1')] = \
+            b'0\x00'
+        self.app.expected_calls[
+                ('vm2', 'admin.vm.property.Set', 'template', b'dummy-1')] = \
+            b'0\x00'
+        self.app.expected_calls[
+                ('vm3', 'admin.vm.property.Set', 'netvm', b'dummy-1')] = \
+            b'0\x00'
+        self.app.expected_calls[
+                ('vm3', 'admin.vm.property.Set', 'template', b'dummy-1')] = \
+            b'0\x00'
+        self.app.expected_calls[
+                ('vm4', 'admin.vm.property.Set', 'netvm', b'dummy-1')] = \
+            b'0\x00'
+        self.app.expected_calls[
+                ('vm4', 'admin.vm.property.Set', 'template', b'dummy-1')] = \
+            b'0\x00'
+        self.app.expected_calls[
+                ('dom0', 'admin.property.Set', 'updatevm', b'')] = \
+            b'0\x00'
+        args = argparse.Namespace(
+            templates=['vm1', 'vm2', 'vm3', 'vm4'],
+            yes=False
+        )
+        def deps(app, vm):
+            if vm == 'vm1':
+                return [(self.app.domains['vm2'], 'default_template'),
+                        (self.app.domains['vm3'], 'netvm')]
+            if vm == 'vm2' or vm == 'vm3':
+                return [(self.app.domains['vm4'], 'netvm')]
+            if vm == 'vm4':
+                return [(None, 'updatevm')]
+            return []
+        with mock.patch('qubesadmin.utils.vm_dependencies') as mock_deps:
+            mock_deps.side_effect = deps
+            qubesadmin.tools.qvm_template.remove(args, self.app, disassoc=True)
+            self.assertEqual(mock_deps.mock_calls, [
+                mock.call(self.app, self.app.domains['vm1']),
+                mock.call(self.app, self.app.domains['vm2']),
+                mock.call(self.app, self.app.domains['vm3']),
+                mock.call(self.app, self.app.domains['vm4'])
+            ])
+        self.assertEqual(mock_confirm.mock_calls, [
+            mock.call(re_str(r'.*completely remove.*'),
+                ['vm1', 'vm2', 'vm3', 'vm4'])
+        ])
+        self.assertEqual(mock_remove.mock_calls, [
+            mock.call(['--force', '--', 'vm1', 'vm2', 'vm3', 'vm4'],
+                self.app)
+        ])
+        self.assertEqual(mock_kill.mock_calls, [
+            mock.call(['--', 'vm1', 'vm2', 'vm3', 'vm4'], self.app)
+        ])
+        self.assertAllCalled()
+
+    def test_213_remove_fail_nodomain(self):
+        self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00vm1 class=TemplateVM state=Halted\n'
+        args = argparse.Namespace(
+            templates=['vm0'],
+            yes=False
+        )
+        with mock.patch('sys.stderr', new=io.StringIO()) as mock_err:
+            with self.assertRaises(SystemExit):
+                qubesadmin.tools.qvm_template.remove(args, self.app)
+            self.assertTrue('no such domain:' in mock_err.getvalue())
+        self.assertAllCalled()

+ 4 - 1
qubesadmin/tools/qvm_template.py

@@ -1360,13 +1360,16 @@ def remove(
 
     if not args.yes:
         repeat = 3 if purge else 1
+        # XXX: Mutating the list later seems to break the tests...
+        remove_list_copy = remove_list.copy()
         for _ in range(repeat):
             confirm_action(
                 'This will completely remove the selected VM(s)...',
-                remove_list)
+                remove_list_copy)
 
     if disassoc:
         # Remove the dummy afterwards if we're purging
+        # as nothing should depend on it in the end
         remove_dummy = purge
         # Create dummy template; handle name collisions
         orig_dummy = dummy