Created
February 3, 2021 17:50
-
-
Save rodrigogansobarbieri/9e40d5746cd18e4c06a3c1cc7aece287 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
"""Unit tests for EvacuationAssistant.""" | |
import ddt | |
import copy | |
import json | |
import mock | |
from evacuation_assistant import evacuation_assistant | |
import random | |
import unittest | |
GROUP_LIST = """ | |
[ | |
{ | |
"ID": "group1", | |
"Policies": "affinity", | |
"Members": "Aff1(1), Aff2(1), Aff3(1)" | |
}, | |
{ | |
"ID": "group2", | |
"Policies": "affinity", | |
"Members": "Aff2(1), Aff4(1), Aff5(1)" | |
}, | |
{ | |
"ID": "group3", | |
"Policies": "affinity", | |
"Members": "Aff6(1), Aff7(1)" | |
}, | |
{ | |
"ID": "group4", | |
"Policies": "affinity", | |
"Members": "Aff7(1), Aff8(3)" | |
}, | |
{ | |
"ID": "group5", | |
"Policies": "anti-affinity", | |
"Members": "antiaff1(1), antiaff2(2), antiaff3(3)" | |
}, | |
{ | |
"ID": "group6", | |
"Policies": "anti-affinity", | |
"Members": "antiaff2(2), antiaff4(1)" | |
}, | |
{ | |
"ID": "group7", | |
"Policies": "anti-affinity", | |
"Members": "antiaff2(2), antiaff6(1)" | |
}, | |
{ | |
"ID": "group8", | |
"Policies": "anti-affinity", | |
"Members": "antiaff6(1), antiaff7(3), antiaff8(4)" | |
}, | |
{ | |
"ID": "group9", | |
"Policies": "anti-affinity", | |
"Members": "antiaff1(1), antiaff3(3), antiaff9(1)" | |
} | |
] | |
""" | |
GROUP_LIST_ERROR = """ | |
[ | |
{ | |
"ID": "group1", | |
"Policies": "affinity", | |
"Members": "Aff1(1), Aff2(2), Aff3(3)" | |
} | |
] | |
""" | |
SERVER_LIST_ERROR = """ | |
[ | |
{ | |
"ID": "Aff1(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "N/A (booted from volume)", | |
"Flavor ID": "1", | |
"Host": "hyp1" | |
}, | |
{ | |
"ID": "Aff2(2)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "1", | |
"Host": "hyp2" | |
}, | |
{ | |
"ID": "Aff3(3)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "1", | |
"Host": "hyp3" | |
} | |
] | |
""" | |
SERVER_LIST = """ | |
[ | |
{ | |
"ID": "Aff1(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "N/A (booted from volume)", | |
"Flavor ID": "1", | |
"Host": "hyp1" | |
}, | |
{ | |
"ID": "Aff2(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "1", | |
"Host": "hyp1" | |
}, | |
{ | |
"ID": "Aff3(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "1", | |
"Host": "hyp1" | |
}, | |
{ | |
"ID": "Aff4(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "N/A (booted from volume)", | |
"Flavor ID": "2", | |
"Host": "hyp1" | |
}, | |
{ | |
"ID": "Aff5(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "2", | |
"Host": "hyp1" | |
}, | |
{ | |
"ID": "Aff6(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "2", | |
"Host": "hyp1" | |
}, | |
{ | |
"ID": "Aff7(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "2", | |
"Host": "hyp1" | |
}, | |
{ | |
"ID": "Aff8(3)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "2", | |
"Host": "hyp3" | |
}, | |
{ | |
"ID": "antiaff1(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "N/A (booted from volume)", | |
"Flavor ID": "1", | |
"Host": "hyp1" | |
}, | |
{ | |
"ID": "antiaff2(2)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "1", | |
"Host": "hyp2" | |
}, | |
{ | |
"ID": "antiaff3(3)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "1", | |
"Host": "hyp3" | |
}, | |
{ | |
"ID": "antiaff4(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "N/A (booted from volume)", | |
"Flavor ID": "2", | |
"Host": "hyp1" | |
}, | |
{ | |
"ID": "antiaff5(3)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "2", | |
"Host": "hyp3" | |
}, | |
{ | |
"ID": "antiaff6(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "2", | |
"Host": "hyp1" | |
}, | |
{ | |
"ID": "antiaff7(3)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "2", | |
"Host": "hyp3" | |
}, | |
{ | |
"ID": "antiaff8(4)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "2", | |
"Host": "hyp4" | |
}, | |
{ | |
"ID": "antiaff9(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "2", | |
"Host": "hyp1" | |
}, | |
{ | |
"ID": "vm1(4)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "image1", | |
"Flavor ID": "2", | |
"Host": "hyp4" | |
}, | |
{ | |
"ID": "vm2(1)", | |
"Status": "ACTIVE", | |
"Power State": "Running", | |
"Image ID": "N/A (booted from volume)", | |
"Flavor ID": "2", | |
"Host": "hyp1" | |
} | |
] | |
""" | |
INVENTORY_LIST = """ | |
[ | |
{ | |
"resource_class": "VCPU", | |
"allocation_ratio": 16.0 | |
}, | |
{ | |
"resource_class": "MEMORY_MB", | |
"allocation_ratio": 1.5 | |
}, | |
{ | |
"resource_class": "DISK_GB", | |
"allocation_ratio": 1.0 | |
} | |
] | |
""" | |
HYPERVISOR_SHOW = """ | |
{ | |
"disk_available_least": 31 | |
} | |
""" | |
HYPERVISOR_LIST = """ | |
[ | |
{ | |
"Hypervisor Hostname": "hyp1", | |
"State": "up", | |
"vCPUs Used": 1, | |
"vCPUs": 2, | |
"Memory MB Used": 576, | |
"Memory MB": 3944 | |
}, | |
{ | |
"Hypervisor Hostname": "hyp2", | |
"State": "up", | |
"vCPUs Used": 2, | |
"vCPUs": 4, | |
"Memory MB Used": 768, | |
"Memory MB": 3944 | |
}, | |
{ | |
"Hypervisor Hostname": "hyp3", | |
"State": "up", | |
"vCPUs Used": 1, | |
"vCPUs": 2, | |
"Memory MB Used": 576, | |
"Memory MB": 3944 | |
}, | |
{ | |
"Hypervisor Hostname": "hyp4", | |
"State": "up", | |
"vCPUs Used": 1, | |
"vCPUs": 2, | |
"Memory MB Used": 576, | |
"Memory MB": 3944 | |
} | |
] | |
""" | |
HYPERVISOR_REMAINING = json.loads(""" | |
{ | |
"Hypervisor Hostname": "hyp2", | |
"State": "up", | |
"vCPUs Used": 1, | |
"vCPUs": 2, | |
"vCPUs Remaining": 31, | |
"Memory MB Used": 576, | |
"Memory MB": 3944, | |
"Memory MB Remaining": 5340, | |
"Disk Remaining": 31, | |
"Inventory": [ | |
{ | |
"resource_class": "VCPU", | |
"allocation_ratio": 16.0 | |
}, | |
{ | |
"resource_class": "MEMORY_MB", | |
"allocation_ratio": 1.5 | |
}, | |
{ | |
"resource_class": "DISK_GB", | |
"allocation_ratio": 1.0 | |
} | |
], | |
"Details": { | |
"disk_available_least": 31 | |
} | |
} | |
""") | |
FLAVOR_LIST = """ | |
[ | |
{ | |
"ID": "1", | |
"RAM": 512, | |
"Disk": 2, | |
"Ephemeral": 1, | |
"VCPUs": 1 | |
}, | |
{ | |
"ID": "2", | |
"RAM": 2048, | |
"Disk": 20, | |
"Ephemeral": 0, | |
"VCPUs": 2 | |
} | |
] | |
""" | |
PROVIDER_LIST = """ | |
[ | |
{ | |
"uuid": "rp1", | |
"name": "hyp1" | |
}, | |
{ | |
"uuid": "rp2", | |
"name": "hyp2" | |
}, | |
{ | |
"uuid": "rp3", | |
"name": "hyp3" | |
}, | |
{ | |
"uuid": "rp4", | |
"name": "hyp4" | |
} | |
] | |
""" | |
PROVIDER_LIST_ERROR = """ | |
[ | |
{ | |
"uuid": "rp2", | |
"name": "hyp2" | |
}, | |
{ | |
"uuid": "rp3", | |
"name": "hyp3" | |
}, | |
{ | |
"uuid": "rp4", | |
"name": "hyp4" | |
} | |
] | |
""" | |
CALCULATED_DESTINATIONS1 = json.loads(""" | |
{ | |
"Aff1(1)": { | |
"aff_vms": [ | |
"Aff2(1)", | |
"Aff3(1)" | |
], | |
"anti_vms": [], | |
"candidate_hosts": [ | |
"hyp2", | |
"hyp3", | |
"hyp4" | |
] | |
}, | |
"Aff2(1)": { | |
"aff_vms": [ | |
"Aff1(1)", | |
"Aff3(1)", | |
"Aff4(1)", | |
"Aff5(1)" | |
], | |
"anti_vms": [], | |
"candidate_hosts": [ | |
"hyp2", | |
"hyp3", | |
"hyp4" | |
] | |
}, | |
"Aff3(1)": { | |
"aff_vms": [ | |
"Aff1(1)", | |
"Aff2(1)" | |
], | |
"anti_vms": [], | |
"candidate_hosts": [ | |
"hyp2", | |
"hyp3", | |
"hyp4" | |
] | |
}, | |
"Aff4(1)": { | |
"aff_vms": [ | |
"Aff2(1)", | |
"Aff5(1)" | |
], | |
"anti_vms": [], | |
"candidate_hosts": [ | |
"hyp2", | |
"hyp3", | |
"hyp4" | |
] | |
}, | |
"Aff5(1)": { | |
"aff_vms": [ | |
"Aff2(1)", | |
"Aff4(1)" | |
], | |
"anti_vms": [], | |
"candidate_hosts": [ | |
"hyp2", | |
"hyp3", | |
"hyp4" | |
] | |
}, | |
"Aff6(1)": { | |
"aff_vms": [ | |
"Aff7(1)" | |
], | |
"anti_vms": [], | |
"candidate_hosts": [ | |
"hyp3" | |
] | |
}, | |
"Aff7(1)": { | |
"aff_vms": [ | |
"Aff6(1)", | |
"Aff8(3)" | |
], | |
"anti_vms": [], | |
"candidate_hosts": [ | |
"hyp3" | |
] | |
}, | |
"antiaff1(1)": { | |
"aff_vms": [], | |
"anti_vms": [ | |
"antiaff2(2)", | |
"antiaff3(3)", | |
"antiaff9(1)" | |
], | |
"candidate_hosts": [ | |
"hyp4" | |
] | |
}, | |
"antiaff4(1)": { | |
"aff_vms": [], | |
"anti_vms": [ | |
"antiaff2(2)" | |
], | |
"candidate_hosts": [ | |
"hyp3", | |
"hyp4" | |
] | |
}, | |
"antiaff6(1)": { | |
"aff_vms": [], | |
"anti_vms": [ | |
"antiaff2(2)", | |
"antiaff7(3)", | |
"antiaff8(4)" | |
], | |
"candidate_hosts": [] | |
}, | |
"antiaff9(1)": { | |
"aff_vms": [], | |
"anti_vms": [ | |
"antiaff1(1)", | |
"antiaff3(3)" | |
], | |
"candidate_hosts": [ | |
"hyp2", | |
"hyp4" | |
] | |
}, | |
"vm2(1)": { | |
"aff_vms": [], | |
"anti_vms": [], | |
"candidate_hosts": [ | |
"hyp2", | |
"hyp3", | |
"hyp4" | |
] | |
} | |
} | |
""") | |
CALCULATED_DESTINATIONS2 = json.loads(""" | |
{ | |
"Aff1(1)": {}, | |
"Aff2(1)": {}, | |
"antiaff1(1)": {} | |
} | |
""") | |
@ddt.ddt | |
class TestEvacuationAssistant(unittest.TestCase): | |
def mock_object(self, obj, attr_name, new_attr=None): | |
"""Helper to avoid using mock decorators.""" | |
if not new_attr: | |
new_attr = mock.Mock() | |
patcher = mock.patch.object(obj, attr_name, new_attr) | |
patcher.start() | |
self.addCleanup(patcher.stop) | |
return new_attr | |
def setUp(self): | |
""""Set up resources common to each test.""" | |
super(TestEvacuationAssistant, self).setUp() | |
self.maxDiff = None | |
self.mock_object(evacuation_assistant, 'LOG') | |
self.run_cmd = self.mock_object(evacuation_assistant, "run_cmd") | |
self.args = evacuation_assistant.parse_args(["--host", "hyp1"]) | |
self.mig = evacuation_assistant.EvacuationAssistant(self.args) | |
self.run_cmd.side_effect = [ | |
PROVIDER_LIST, GROUP_LIST, SERVER_LIST, HYPERVISOR_LIST, | |
FLAVOR_LIST, | |
] | |
def test_gather_info_error_placement1(self): | |
mig = evacuation_assistant.EvacuationAssistant(self.args) | |
self.run_cmd.side_effect = evacuation_assistant.CommandFailedException | |
self.assertRaises(SystemExit, mig.gather_info) | |
def test_gather_info_error_placement2(self): | |
mig = evacuation_assistant.EvacuationAssistant(self.args) | |
self.run_cmd.side_effect = [("openstack: 'resource provider foo " | |
"--format json' is not an openstack " | |
"command. See 'openstack --help'.")] | |
self.assertRaises(SystemExit, mig.gather_info) | |
def test_gather_info_error_evac_host(self): | |
mig = evacuation_assistant.EvacuationAssistant(self.args) | |
self.run_cmd.side_effect = [PROVIDER_LIST_ERROR] | |
self.assertRaises(SystemExit, mig.gather_info) | |
def test_calculate_destinations(self): | |
self.mig.gather_info() | |
result = self.mig.calculate_destinations() | |
self.assertEqual(CALCULATED_DESTINATIONS1, result) | |
def test_calculate_destinations_affinity_destination_conflict(self): | |
self.run_cmd.side_effect = [ | |
PROVIDER_LIST, GROUP_LIST_ERROR, SERVER_LIST_ERROR, | |
HYPERVISOR_LIST, FLAVOR_LIST, | |
] | |
self.mig.gather_info() | |
self.assertRaises(evacuation_assistant.AffinityDestinationConflict, | |
self.mig.calculate_destinations) | |
def test_select_hosts(self): | |
self.mig.gather_info() | |
self.mig.dest_map = copy.deepcopy(CALCULATED_DESTINATIONS1) | |
self.mig.in_progress = { | |
"Aff1(1)": "hyp2", | |
"antiaff1(1)": "hyp4", | |
} | |
expected1 = 'hyp2' | |
expected2 = 'hyp3' | |
self.mock_object(self.mig, 'filter_hosts', mock.Mock( | |
return_value=['hyp3', 'hyp4'])) | |
self.assertEqual(expected1, self.mig.select_host("Aff2(1)")) | |
self.assertEqual(expected2, self.mig.select_host("antiaff4(1)")) | |
self.mig.filter_hosts.assert_called_once_with( | |
['hyp3', 'hyp4'], 'antiaff4(1)') | |
def test_select_host_antiaffinity_violated(self): | |
self.mig.gather_info() | |
self.mig.dest_map = copy.deepcopy(CALCULATED_DESTINATIONS1) | |
self.mig.in_progress = { | |
"antiaff1(1)": "hyp4", | |
} | |
self.mock_object(self.mig, 'filter_hosts', mock.Mock( | |
return_value=['hyp2'])) | |
self.assertEqual('hyp2', self.mig.select_host("antiaff9(1)")) | |
self.mig.filter_hosts.assert_called_once_with( | |
['hyp2'], 'antiaff9(1)') | |
def test_calculate_remaining_resources(self): | |
self.run_cmd.side_effect = [ | |
PROVIDER_LIST, GROUP_LIST, SERVER_LIST, HYPERVISOR_LIST, | |
FLAVOR_LIST, INVENTORY_LIST, HYPERVISOR_SHOW, | |
] | |
self.mig.gather_info() | |
expected = { | |
"Hypervisor Hostname": "hyp2", | |
"State": "up", | |
"vCPUs Used": 2, | |
"vCPUs": 4, | |
"vCPUs Remaining": 62, | |
"Memory MB Used": 768, | |
"Memory MB": 3944, | |
"Memory MB Remaining": 5148, | |
"Disk Remaining": 31, | |
"Inventory": [ | |
{ | |
"resource_class": "VCPU", | |
"allocation_ratio": 16.0 | |
}, | |
{ | |
"resource_class": "MEMORY_MB", | |
"allocation_ratio": 1.5 | |
}, | |
{ | |
"resource_class": "DISK_GB", | |
"allocation_ratio": 1.0 | |
} | |
], | |
"Details": { | |
"disk_available_least": 31 | |
}, | |
} | |
self.mig.calculate_initial_remaining_resources("hyp2") | |
self.assertEqual(expected, self.mig.hosts_indexed['hyp2']) | |
def test_adjust_remaining_resources(self): | |
self.mig.gather_info() | |
self.mig.hosts_indexed['hyp2'] = copy.deepcopy(HYPERVISOR_REMAINING) | |
expected = copy.deepcopy(HYPERVISOR_REMAINING) | |
self.mig.adjust_remaining_resources("antiaff5(3)", 'hyp2') | |
expected.update({ | |
"Memory MB Remaining": 3292, | |
"vCPUs Remaining": 29, | |
"Disk Remaining": 11, | |
}) | |
self.assertEqual(expected, self.mig.hosts_indexed['hyp2']) | |
self.mig.adjust_remaining_resources("antiaff1(1)", 'hyp2') | |
expected.update({ | |
"Memory MB Remaining": 2780, | |
"vCPUs Remaining": 28, | |
}) | |
self.assertEqual(expected, self.mig.hosts_indexed['hyp2']) | |
def test_filter_hosts(self): | |
self.mig.gather_info() | |
self.mig.hosts_indexed['hyp3'] = copy.deepcopy(HYPERVISOR_REMAINING) | |
hyp = copy.deepcopy(HYPERVISOR_REMAINING) | |
hyp.update({ | |
'Memory MB remaining': 1536, | |
'Disk Remaining': 10, | |
}) | |
self.mig.hosts_indexed['hyp2'] = hyp | |
self.assertEqual( | |
['hyp3'], self.mig.filter_hosts(['hyp2', 'hyp3'], "antiaff5(3)")) | |
self.assertEqual( | |
['hyp2', 'hyp3'], | |
self.mig.filter_hosts(['hyp2', 'hyp3'], "antiaff1(1)")) | |
def test_is_volume_backed(self): | |
self.mig.gather_info() | |
self.assertEqual(False, self.mig.is_volume_backed("antiaff5(3)")) | |
self.assertEqual(True, self.mig.is_volume_backed("antiaff1(1)")) | |
def test_build_cmd(self): | |
self.mig.gather_info() | |
self.mig.dest_map = copy.deepcopy(CALCULATED_DESTINATIONS1) | |
expected1 = "openstack server migrate Aff4(1) --live hyp3" | |
expected2 = ("openstack server migrate antiaff6(1) --block-migration " | |
"--live-migration --host hyp4 " | |
"--os-compute-api-version 2.30") | |
self.assertEqual(expected1, self.mig.build_cmd('hyp3', 'Aff4(1)')) | |
self.assertEqual(expected2, self.mig.build_cmd('hyp4', 'antiaff6(1)')) | |
def test_run(self): | |
self.mock_object(self.mig, 'check_dependencies') | |
self.mock_object(self.mig, 'evacuate') | |
self.mock_object(self.mig, 'gather_info') | |
self.mock_object(self.mig, 'calculate_destinations') | |
self.mig.run() | |
self.mig.check_dependencies.assert_called_once_with() | |
self.mig.evacuate.assert_called_once_with() | |
self.mig.gather_info.assert_called_once_with() | |
self.mig.calculate_destinations.assert_called_once_with() | |
def test_evacuate(self): | |
self.mig.gather_info() | |
self.mig.dest_map = copy.deepcopy(CALCULATED_DESTINATIONS2) | |
def _build_cmd_side_effect(host, vm_id): | |
return "cmd_{}_{}".format(vm_id, host) | |
self.mock_object(self.mig, 'calculate_initial_remaining_resources') | |
self.mock_object(self.mig, 'select_host', mock.Mock( | |
side_effect=[None, 'hyp1', 'hyp3'])) | |
self.mock_object(self.mig, 'build_cmd', mock.Mock( | |
side_effect=_build_cmd_side_effect)) | |
self.mock_object(self.mig, 'adjust_remaining_resources') | |
self.mig.evacuate() | |
self.mig.calculate_initial_remaining_resources.assert_has_calls([ | |
mock.call(host) for host in self.mig.hosts_indexed.keys() | |
]) | |
self.mig.select_host.assert_has_calls([ | |
mock.call(vm_id) for vm_id in self.mig.dest_map.keys() | |
]) | |
self.mig.build_cmd.assert_has_calls([ | |
mock.call('hyp1', 'Aff2(1)'), | |
mock.call('hyp3', 'antiaff1(1)'), | |
]) | |
self.mig.adjust_remaining_resources.assert_has_calls([ | |
mock.call('Aff2(1)', 'hyp1'), | |
mock.call('antiaff1(1)', 'hyp3'), | |
]) | |
expected_inprogress = { | |
'Aff2(1)': 'hyp1', | |
'antiaff1(1)': 'hyp3', | |
} | |
self.assertEqual(expected_inprogress, self.mig.in_progress) | |
evacuation_assistant.LOG.error.assert_called_once_with( | |
"Failed to find a destination host for the following VMs: " | |
"\nAff1(1)" | |
) | |
@mock.patch.dict(evacuation_assistant.os.environ, | |
{'OS_AUTH_URL': 'http://keystone', 'foo': 'bar'}) | |
def test_check_dependencies(self): | |
self.mig.check_dependencies() | |
@mock.patch.dict(evacuation_assistant.os.environ, {'foo': 'bar'}) | |
def test_check_dependencies_exit(self): | |
self.assertRaises(SystemExit, self.mig.check_dependencies) | |
class Pipes(object): | |
"""Fake class for testing subprocess.Popen""" | |
def __init__(self, return_code): | |
self.stdout = b'stdout test' | |
self.stderr = b'stderr test' | |
self.returncode = return_code | |
def communicate(self): | |
"""Returns test data.""" | |
return self.stdout, self.stderr | |
@ddt.ddt | |
class TestModule(unittest.TestCase): | |
@ddt.data(0, 1) | |
@mock.patch.object(evacuation_assistant.subprocess, 'Popen') | |
def test_run_cmd(self, return_code, popen): | |
popen.return_value = Pipes(return_code) | |
if return_code == 1: | |
self.assertRaises( | |
evacuation_assistant.CommandFailedException, | |
evacuation_assistant.run_cmd, "foo bar test") | |
else: | |
evacuation_assistant.run_cmd("foo bar test") | |
popen.assert_called_once_with(['foo', 'bar', 'test'], | |
stdout=-1, stderr=-1) | |
if __name__ == '__main__': | |
unittest.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment