class TestPersistency(BaseDealerTest): def setup(self): self._mock_dealer_functions() def test_save(self): # Verify a call to save with or without loading banks. for banks in ([], [BANK_PATH_1, ], [BANK_PATH_1, BANK_PATH_2, ]): yield (self._check_save, False, banks, []) yield (self._check_save, True, banks, banks) def test_error_unpickling(self): self._setup_bank(BANK_PATH_1, False, False, SCENARIO_1_2) mocked_load = Mock(side_effect = IOError()) with patch("bddbot.dealer.pickle.load", mocked_load): self.dealer = Dealer([BANK_PATH_1, ], DEFAULT_TEST_COMMANDS) self.mocked_open.assert_called_once_with(STATE_PATH, "rb") self._reset_mocks() # Make sure dealer was not loaded by calling load again. with patch("bddbot.dealer.Bank", self.mock_bank_class): self.dealer.load() self.mock_bank_class.assert_called_once_with(BANK_PATH_1) def test_resume(self): self._setup_bank(BANK_PATH_1, False, False, SCENARIO_1_2) # Load a dealer's state. with patch("bddbot.dealer.pickle.load") as mocked_load: mocked_load.return_value = self.mock_banks.values() self.dealer = Dealer([], DEFAULT_TEST_COMMANDS) self.mocked_open.assert_called_once_with(STATE_PATH, "rb") mocked_load.assert_called_once_with(ANY) self.mocked_popen.assert_not_called() # Reset the open mock before calling `_deal`, required for assertions. self.mocked_open.reset_mock() # Verify successful loading by dealing from the bank. popen_calls = self._deal(None, SCENARIO_1_2, BANK_PATH_1) assert_equal(DEFAULT_TEST_COMMANDS, popen_calls) def _check_save(self, should_load, bank_paths, expected_banks): if not should_load: self._create_dealer(bank_paths, None) else: self._load_dealer(banks = bank_paths) with patch("bddbot.dealer.pickle.dump") as mocked_dump: self.dealer.save() self.mocked_open.assert_called_once_with(STATE_PATH, "wb") mocked_dump.assert_called_once_with( [self.mock_banks[path] for path in expected_banks], self.mocked_open[STATE_PATH]) self.mocked_popen.assert_not_called()
class BaseDealerTest(BankMockerTest): # pylint: disable=too-few-public-methods """A container for utility classes common when testing the Dealer class.""" FEATURES = { BANK_PATH_1: (FEATURE_PATH_1, "", FEATURE_1 + "\n"), BANK_PATH_2: (FEATURE_PATH_2, "", FEATURE_2 + "\n"), } def __init__(self): super(BaseDealerTest, self).__init__() self.dealer = None self.mocked_open = MockOpen() self.mocked_popen = create_autospec(Popen) self.mocked_mkdir = Mock() def teardown(self): super(BaseDealerTest, self).teardown() patch.stopall() # Reset dealer instance. self.dealer = None def _mock_dealer_functions(self): """Mock out standard library functions used by the dealer module.""" self._reset_mocks() patcher = patch.multiple( "bddbot.dealer", open = self.mocked_open, Bank = self.mock_bank_class, RemoteBank = self.mock_bank_class, Popen = self.mocked_popen, mkdir = self.mocked_mkdir) patcher.start() def _create_dealer(self, banks, tests, name = ""): """Create a new dealer instance without loading state.""" if tests is None: tests = DEFAULT_TEST_COMMANDS self.mocked_open[STATE_PATH].side_effect = IOError() self.dealer = Dealer(banks, tests, name = name) self.mocked_open.assert_called_once_with(STATE_PATH, "rb") self._reset_mocks() def _load_dealer(self, banks = None, tests = None, name = ""): """Simulate a call to load() and verify success.""" if banks is None: banks = [BANK_PATH_1, ] if not tests: tests = DEFAULT_TEST_COMMANDS if self.dealer is None: self._create_dealer(banks, tests, name = name) # pylint: disable=bad-continuation with patch.multiple("bddbot.dealer", Bank = self.mock_bank_class, RemoteBank = self.mock_bank_class): self.dealer.load() # Verify calls to mocks. self.mocked_open.assert_not_called() self.mocked_popen.assert_not_called() for path in banks: if not path.startswith("@"): self.mock_bank_class.assert_any_call(path) else: (host, port) = path[1:].split(":") self.mock_bank_class.assert_called_with(name, host, int(port)) self._reset_mocks() def _deal(self, expected_feature, expected_scenario, bank_path = None, feature_path = None): # pylint: disable=too-many-arguments """Simulate dealing a scenario and verify success. If `expected_feature` is specified, simulate the first time a scenario is dealt from the features bank. Otherwise, simulate a consecutive deal from a previous bank. Return the commands passed to `Popen()` to verify outside of this function. """ if not bank_path: bank_path = BANK_PATH_1 if not feature_path: feature_path = bank_path.replace("bank", "feature") self.mocked_popen.return_value.returncode = 0 self.mocked_popen.return_value.communicate.return_value = ("", "") self.dealer.deal() # If feature is specified, simulate the first deal from the features bank. if expected_feature is not None: self.mocked_open.assert_called_once_with(feature_path, "w") self._assert_writes( ["", expected_feature + "\n", expected_scenario, ], path = feature_path) self.mocked_mkdir.assert_called_once_with(dirname(feature_path)) # If feature isn't specified, simulate a consecutive deal. # Note that calls to Popen should be verified outside of this function in this case. else: self.mocked_open.assert_called_once_with(feature_path, "ab") self.mocked_open[feature_path].write.assert_called_once_with(expected_scenario) self.mocked_popen.return_value.communicate.assert_called_with() self.mocked_mkdir.assert_not_called() self.mock_banks[bank_path].is_fresh.assert_called_with() self.mock_banks[bank_path].is_done.assert_called_with() self.mock_banks[bank_path].get_next_scenario.assert_called_once_with() # We return the commands because we reset the mocks at the end of the function. # The stdout/stderr values aren't important, we only care about the commands. popen_calls = [command for ((command, ), _) in self.mocked_popen.call_args_list] # Reset mocks. self._reset_mocks() return popen_calls def _assert_writes(self, chunks, path = FEATURE_PATH_1): """Verify all calls to write().""" assert_equal([call(chunk) for chunk in chunks], self.mocked_open[path].write.mock_calls) def _reset_mocks(self): """Reset all mocks.""" self.mocked_open.reset_mock() self.mocked_popen.reset_mock() self.mock_bank_class.reset_mock() self.mocked_mkdir.reset_mock() for mock_bank in self.mock_banks.itervalues(): mock_bank.reset_mock()