Exemplo n.º 1
0
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()
Exemplo n.º 2
0
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()