@@ -83,8 +83,6 @@ def test_004_multiple_vms(self):
8383 app = self .app )
8484 self .assertAllCalled ()
8585
86- @unittest .skipUnless (qubesadmin .tools .qvm_shutdown .have_events ,
87- 'Events not present' )
8886 def test_010_wait (self ):
8987 '''test --wait option'''
9088 loop = asyncio .new_event_loop ()
@@ -114,8 +112,6 @@ def test_010_wait(self):
114112 qubesadmin .tools .qvm_shutdown .main (['--wait' , 'some-vm' ], app = self .app )
115113 self .assertAllCalled ()
116114
117- @unittest .skipUnless (qubesadmin .tools .qvm_shutdown .have_events ,
118- 'Events not present' )
119115 def test_012_wait_all (self ):
120116 '''test --wait option, with multiple VMs'''
121117 loop = asyncio .new_event_loop ()
@@ -161,8 +157,6 @@ def test_012_wait_all(self):
161157 qubesadmin .tools .qvm_shutdown .main (['--wait' , '--all' ], app = self .app )
162158 self .assertAllCalled ()
163159
164- @unittest .skipUnless (qubesadmin .tools .qvm_shutdown .have_events ,
165- 'Events not present' )
166160 def test_015_wait_all_kill_timeout (self ):
167161 '''test --wait option, with multiple VMs and killing on timeout'''
168162 loop = asyncio .new_event_loop ()
@@ -251,3 +245,155 @@ def test_017_all_exclude_force_explicit(self):
251245 '--force' ],
252246 app = self .app )
253247 self .assertAllCalled ()
248+
249+ def test_005_force (self ):
250+ '''test --force sends force flag to shutdown call'''
251+ self .app .expected_calls [
252+ ('dom0' , 'admin.vm.List' , None , None )] = \
253+ b'0\x00 some-vm class=AppVM state=Running\n '
254+ self .app .expected_calls [
255+ ('some-vm' , 'admin.vm.Shutdown' , 'force' , None )] = b'0\x00 '
256+ qubesadmin .tools .qvm_shutdown .main (
257+ ['--force' , 'some-vm' ], app = self .app )
258+ self .assertAllCalled ()
259+
260+ def test_006_dry_run (self ):
261+ '''test --dry-run skips shutdown calls'''
262+ self .app .expected_calls [
263+ ('dom0' , 'admin.vm.List' , None , None )] = \
264+ b'0\x00 some-vm class=AppVM state=Running\n '
265+ qubesadmin .tools .qvm_shutdown .main (
266+ ['--dry-run' , 'some-vm' ], app = self .app )
267+ self .assertAllCalled ()
268+
269+ def test_011_wait_retry (self ):
270+ '''test --wait retries VMs whose shutdown request failed'''
271+ loop = asyncio .new_event_loop ()
272+ asyncio .set_event_loop (loop )
273+
274+ mock_events = unittest .mock .AsyncMock ()
275+ patch = unittest .mock .patch (
276+ 'qubesadmin.events.EventsDispatcher._get_events_reader' ,
277+ mock_events )
278+ patch .start ()
279+ self .addCleanup (patch .stop )
280+ mock_events .side_effect = qubesadmin .tests .tools .MockEventsReader ([
281+ # round 1: wait for some-vm
282+ b'1\0 \0 connection-established\0 \0 ' ,
283+ b'1\0 some-vm\0 domain-shutdown\0 \0 ' ,
284+ # round 2: wait for other-vm
285+ b'1\0 \0 connection-established\0 \0 ' ,
286+ b'1\0 other-vm\0 domain-shutdown\0 \0 ' ,
287+ ])
288+
289+ self .app .expected_calls [
290+ ('dom0' , 'admin.vm.List' , None , None )] = \
291+ b'0\x00 ' \
292+ b'some-vm class=AppVM state=Running\n ' \
293+ b'other-vm class=AppVM state=Running\n '
294+ self .app .expected_calls [
295+ ('some-vm' , 'admin.vm.Shutdown' , None , None )] = \
296+ b'0\x00 '
297+ # other-vm fails first attempt, succeeds on retry
298+ self .app .expected_calls [
299+ ('other-vm' , 'admin.vm.Shutdown' , None , None )] = [
300+ b'2\x00 QubesException\x00 \x00 Shutdown refused\x00 ' ,
301+ b'0\x00 ' ,
302+ ]
303+ self .app .expected_calls [
304+ ('some-vm' , 'admin.vm.CurrentState' , None , None )] = [
305+ b'0\x00 power_state=Running' ,
306+ b'0\x00 power_state=Halted' ,
307+ ]
308+ self .app .expected_calls [
309+ ('other-vm' , 'admin.vm.CurrentState' , None , None )] = [
310+ b'0\x00 power_state=Running' ,
311+ b'0\x00 power_state=Halted' ,
312+ ]
313+ qubesadmin .tools .qvm_shutdown .main (
314+ ['--wait' , 'some-vm' , 'other-vm' ], app = self .app )
315+ self .assertAllCalled ()
316+
317+ def test_013_wait_all_shutdown_fail (self ):
318+ '''test --wait exits with error when all shutdown requests fail'''
319+ self .app .expected_calls [
320+ ('dom0' , 'admin.vm.List' , None , None )] = \
321+ b'0\x00 some-vm class=AppVM state=Running\n '
322+ self .app .expected_calls [
323+ ('some-vm' , 'admin.vm.Shutdown' , None , None )] = \
324+ b'2\x00 QubesException\x00 \x00 Shutdown refused\x00 '
325+ self .app .expected_calls [
326+ ('some-vm' , 'admin.vm.CurrentState' , None , None )] = \
327+ b'0\x00 power_state=Running'
328+ with self .assertRaises (SystemExit ):
329+ qubesadmin .tools .qvm_shutdown .main (
330+ ['--wait' , 'some-vm' ], app = self .app )
331+ self .assertAllCalled ()
332+
333+ def test_016_wait_kill_exception (self ):
334+ '''test --wait timeout where kill raises QubesException'''
335+ loop = asyncio .new_event_loop ()
336+ asyncio .set_event_loop (loop )
337+
338+ mock_events = unittest .mock .AsyncMock ()
339+ patch = unittest .mock .patch (
340+ 'qubesadmin.events.EventsDispatcher._get_events_reader' ,
341+ mock_events )
342+ patch .start ()
343+ self .addCleanup (patch .stop )
344+ mock_events .side_effect = qubesadmin .tests .tools .MockEventsReader ([
345+ b'1\0 \0 connection-established\0 \0 ' ,
346+ ])
347+
348+ self .app .expected_calls [
349+ ('dom0' , 'admin.vm.List' , None , None )] = \
350+ b'0\x00 some-vm class=AppVM state=Running\n '
351+ self .app .expected_calls [
352+ ('some-vm' , 'admin.vm.Shutdown' , None , None )] = \
353+ b'0\x00 '
354+ self .app .expected_calls [
355+ ('some-vm' , 'admin.vm.Kill' , None , None )] = \
356+ b'2\x00 QubesException\x00 \x00 Kill failed\x00 '
357+ self .app .expected_calls [
358+ ('some-vm' , 'admin.vm.CurrentState' , None , None )] = [
359+ b'0\x00 power_state=Running' ,
360+ b'0\x00 power_state=Running' ,
361+ ]
362+ with self .assertRaises (SystemExit ):
363+ qubesadmin .tools .qvm_shutdown .main (
364+ ['--wait' , '--timeout=1' , 'some-vm' ], app = self .app )
365+ self .assertAllCalled ()
366+
367+ def test_017_wait_dispvm_na (self ):
368+ '''test --wait treats DispVM with NA power state as shut down'''
369+ loop = asyncio .new_event_loop ()
370+ asyncio .set_event_loop (loop )
371+
372+ mock_events = unittest .mock .AsyncMock ()
373+ patch = unittest .mock .patch (
374+ 'qubesadmin.events.EventsDispatcher._get_events_reader' ,
375+ mock_events )
376+ patch .start ()
377+ self .addCleanup (patch .stop )
378+ mock_events .side_effect = qubesadmin .tests .tools .MockEventsReader ([
379+ b'1\0 \0 connection-established\0 \0 ' ,
380+ b'1\0 disp123\0 domain-shutdown\0 \0 ' ,
381+ ])
382+
383+ self .app .expected_calls [
384+ ('dom0' , 'admin.vm.List' , None , None )] = \
385+ b'0\x00 disp123 class=DispVM state=Running\n '
386+ self .app .expected_calls [
387+ ('disp123' , 'admin.vm.Shutdown' , None , None )] = \
388+ b'0\x00 '
389+ self .app .expected_calls [
390+ ('disp123' , 'admin.vm.CurrentState' , None , None )] = [
391+ b'0\x00 power_state=Running' ,
392+ # failed_domains: first get_power_state() != 'Halted',
393+ # then klass == 'DispVM' triggers second get_power_state()
394+ b'0\x00 power_state=NA' ,
395+ b'0\x00 power_state=NA' ,
396+ ]
397+ qubesadmin .tools .qvm_shutdown .main (
398+ ['--wait' , 'disp123' ], app = self .app )
399+ self .assertAllCalled ()
0 commit comments