<commit_before># -*- coding: utf-8 -*-
import unittest
from .base import FunctionalTestCase
from .pages import game

class CreatePlayerTests(FunctionalTestCase):
    """Tests for creating players at the start of a game"""
    def test_can_create_player(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        page = game.Homepage(self.browser)
        page.start_button.click()
        self.assertRegex(self.browser.current_url, r'/game/([^/]+)$')

        self.story('She sees an add player button')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()

        self.story('She lands on a add player page')
        add_player = game.AddPlayerPage(self.browser)
        self.assertRegex(self.browser.current_url,
            r'/game/([^/]+)/add-player$')
        self.assertEqual(add_player.header.text, 'Add player')
        self.assertEqual(self.browser.title, 'Add player')

        self.story('The game field is not visible')
        #self.assertEqual(add_player.game.get_attribute('type'), 'hidden')
        self.story('She enters her name and a starting amount of cash')
        add_player.name.clear()
        add_player.name.send_keys('Alice')
        add_player.cash.clear()
        add_player.cash.send_keys('700')
        add_player.add_button.click()

        self.story('Her name appears in the player list on the game page')
        self.assertRegex(self.browser.current_url, r'/game/([^/]+)$')

        player_list = game_page.get_players()
        self.assertEqual(len(player_list), 1)
        self.assertEqual(player_list[0]['name'].text, 'Alice')
        self.assertEqual(player_list[0]['cash'].text, '700')

    def test_cannot_create_duplicate_player(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        page = game.Homepage(self.browser)
        page.start_button.click()

        self.story('She goes to add a player')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()
        add_player = game.AddPlayerPage(self.browser)
        add_player.name.clear()
        add_player.cash.clear()
        add_player.name.send_keys('Alice')
        add_player.cash.send_keys('700\n')
        self.story('The new player is added to the game')
        player_list = game_page.get_players()
        self.assertEqual(len(player_list), 1)
        self.assertEqual(player_list[0]['name'].text, 'Alice')
        self.assertEqual(player_list[0]['cash'].text, '700')

        self.story('She goes to add another player')
        game_page.add_player_link.click()
        add_player.name.clear()
        add_player.cash.clear()
        add_player.name.send_keys('Alice')
        add_player.cash.send_keys('100\n')
        self.story('She stays on the same page and sees an error message')
        self.assertRegex(self.browser.current_url,
            r'/game/([^/]+)/add-player$')
        self.assertIn('There is already a player with this name in your game',
            add_player.error_list.text)

    def test_can_return_to_game_page_from_add_player_page(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        page = game.Homepage(self.browser)
        page.start_button.click()

        self.story('She goes to the add player screen')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()
        self.assertRegex(self.browser.current_url,
            r'/game/([^/]+)/add-player$')

        self.story("She doesn't want to add a player and clicks the back "
            "button")
        add_player = game.AddPlayerPage(self.browser)
        add_player.back.click()
        self.assertEqual(self.browser.title, '18xx Accountant')
        self.assertRegex(self.browser.current_url, r'/game/([^/]+)$')
        self.assertEqual(game_page.get_players(), [])

    def test_creating_player_with_cash_decreases_bank_cash(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        page = game.Homepage(self.browser)
        page.bank_cash.clear()
        page.bank_cash.send_keys('1000\n')

        self.story('She goes to add a player')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()

        add_player = game.AddPlayerPage(self.browser)
        add_player.name.clear()
        add_player.cash.clear()
        add_player.name.send_keys('Alice')
        add_player.cash.send_keys('400\n')

        self.story('On the game page she sees that the bank size has reduced')
        self.assertEqual(game_page.bank_cash.text, '600')

class ManagePlayerTests(FunctionalTestCase):
    """Tests for managing player actions during a game"""
    def test_clicking_player_opens_player_detail_section(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        homepage = game.Homepage(self.browser)
        homepage.start_button.click()

        self.story('She adds two players')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()
        add_player = game.AddPlayerPage(self.browser)
        add_player.name.clear()
        add_player.name.send_keys('Alice\n')

        game_page.add_player_link.click()
        add_player.name.clear()
        add_player.name.send_keys('Bob\n')

        self.story('Check that the two players are in the list')
        self.assertCountEqual(['Alice', 'Bob'],
            list(player.text for player in game_page.player_name_list))

        self.story('The player detail sections are both hidden')
        players = game_page.get_players()
        for player in players:
            with self.subTest(player=player['name'].text):
                self.assertIsNone(player['detail'])

        self.story('She clicks the first player and the details appear')
        players[0]['row'].click()
        players = game_page.get_players() # Get DOM updates
        self.assertIsNotNone(players[0]['detail'])
        self.assertIsNone(players[1]['detail'])

        self.story("She clicks the second player, the first player's details"
            "disappear and the second player's appear")
        players[1]['row'].click()
        players = game_page.get_players() # Get DOM updates
        self.assertIsNone(players[0]['detail'])
        self.assertIsNotNone(players[1]['detail'])

    def test_clicking_player_closes_company_detail_section(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        homepage = game.Homepage(self.browser)
        homepage.start_button.click()

        self.story('She adds a player')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()
        add_player = game.AddPlayerPage(self.browser)
        add_player.name.send_keys('Alice\n')

        self.story('She also adds a company')
        game_page.add_company_link.click()
        add_company = game.AddCompanyPage(self.browser)
        add_company.name.send_keys('PRR\n')

        self.story('She clicks the company to open its detail section')
        player = game_page.get_players()[0]
        company = game_page.get_companies()[0]
        company['elem'].click()

        # Need to retrieve DOM updates first
        company = game_page.get_companies()[0]
        player = game_page.get_players()[0]
        self.assertIsNone(player['detail'])
        self.assertIsNotNone(company['detail'])

        self.story('When she clicks the player the company detail closes')
        player['row'].click()
        player = game_page.get_players()[0]
        company = game_page.get_companies()[0]
        self.assertIsNotNone(player['detail'])
        self.assertIsNone(company['detail'])

    def test_player_can_transfer_money_to_bank(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        homepage = game.Homepage(self.browser)
        homepage.start_button.click()

        self.story('She adds a player')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()
        add_player = game.AddPlayerPage(self.browser)
        add_player.name.send_keys('Alice')
        add_player.cash.send_keys('1000\n')

        self.story('Confirm cash amounts')
        player = game_page.get_players()[0]
        self.assertEqual(player['cash'].text, '1000')
        self.assertEqual(game_page.bank_cash.text, '11000')
        self.story("She opens the player's detail view")
        player['row'].click()
        player = game_page.get_players()[0]

        self.story('There is a form that allows her to send money')
        transfer_form = game.TransferForm(self.browser)
        transfer_form.amount(player['detail']).clear()
        transfer_form.amount(player['detail']).send_keys('100')
        for radio in transfer_form.target(player['detail']):
            if radio.get_attribute('value') == 'bank':
                radio.click()
                break
        transfer_form.transfer_button(player['detail']).click()

        self.story("The page reloads and the player's cash amount has lowered")
        player = game_page.get_players()[0]
        self.assertEqual(player['cash'].text, '900')
        self.assertEqual(game_page.bank_cash.text, '11100')

        self.story('Alice goes to transfer money again')
        player['row'].click()
        player = game_page.get_players()[0] # Get DOM updates
        transfer_form.amount(player['detail']).clear()
        transfer_form.amount(player['detail']).send_keys('10')
        self.story("This time she doesn't select a target, the bank should be"
            ' default')
        transfer_form.transfer_button(player['detail']).click()

        self.story('After the page reloads she has found that the amount has'
            'changed again')
        player = game_page.get_players()[0]
        self.assertEqual(player['cash'].text, '890')
        self.assertEqual(game_page.bank_cash.text, '11110')
<commit_msg>Add FT for transfering money from player to company<commit_after># -*- coding: utf-8 -*-
import unittest
from .base import FunctionalTestCase
from .pages import game

class CreatePlayerTests(FunctionalTestCase):
    """Tests for creating players at the start of a game"""
    def test_can_create_player(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        page = game.Homepage(self.browser)
        page.start_button.click()
        self.assertRegex(self.browser.current_url, r'/game/([^/]+)$')

        self.story('She sees an add player button')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()

        self.story('She lands on a add player page')
        add_player = game.AddPlayerPage(self.browser)
        self.assertRegex(self.browser.current_url,
            r'/game/([^/]+)/add-player$')
        self.assertEqual(add_player.header.text, 'Add player')
        self.assertEqual(self.browser.title, 'Add player')

        self.story('The game field is not visible')
        #self.assertEqual(add_player.game.get_attribute('type'), 'hidden')
        self.story('She enters her name and a starting amount of cash')
        add_player.name.clear()
        add_player.name.send_keys('Alice')
        add_player.cash.clear()
        add_player.cash.send_keys('700')
        add_player.add_button.click()

        self.story('Her name appears in the player list on the game page')
        self.assertRegex(self.browser.current_url, r'/game/([^/]+)$')

        player_list = game_page.get_players()
        self.assertEqual(len(player_list), 1)
        self.assertEqual(player_list[0]['name'].text, 'Alice')
        self.assertEqual(player_list[0]['cash'].text, '700')

    def test_cannot_create_duplicate_player(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        page = game.Homepage(self.browser)
        page.start_button.click()

        self.story('She goes to add a player')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()
        add_player = game.AddPlayerPage(self.browser)
        add_player.name.clear()
        add_player.cash.clear()
        add_player.name.send_keys('Alice')
        add_player.cash.send_keys('700\n')
        self.story('The new player is added to the game')
        player_list = game_page.get_players()
        self.assertEqual(len(player_list), 1)
        self.assertEqual(player_list[0]['name'].text, 'Alice')
        self.assertEqual(player_list[0]['cash'].text, '700')

        self.story('She goes to add another player')
        game_page.add_player_link.click()
        add_player.name.clear()
        add_player.cash.clear()
        add_player.name.send_keys('Alice')
        add_player.cash.send_keys('100\n')
        self.story('She stays on the same page and sees an error message')
        self.assertRegex(self.browser.current_url,
            r'/game/([^/]+)/add-player$')
        self.assertIn('There is already a player with this name in your game',
            add_player.error_list.text)

    def test_can_return_to_game_page_from_add_player_page(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        page = game.Homepage(self.browser)
        page.start_button.click()

        self.story('She goes to the add player screen')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()
        self.assertRegex(self.browser.current_url,
            r'/game/([^/]+)/add-player$')

        self.story("She doesn't want to add a player and clicks the back "
            "button")
        add_player = game.AddPlayerPage(self.browser)
        add_player.back.click()
        self.assertEqual(self.browser.title, '18xx Accountant')
        self.assertRegex(self.browser.current_url, r'/game/([^/]+)$')
        self.assertEqual(game_page.get_players(), [])

    def test_creating_player_with_cash_decreases_bank_cash(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        page = game.Homepage(self.browser)
        page.bank_cash.clear()
        page.bank_cash.send_keys('1000\n')

        self.story('She goes to add a player')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()

        add_player = game.AddPlayerPage(self.browser)
        add_player.name.clear()
        add_player.cash.clear()
        add_player.name.send_keys('Alice')
        add_player.cash.send_keys('400\n')

        self.story('On the game page she sees that the bank size has reduced')
        self.assertEqual(game_page.bank_cash.text, '600')

class ManagePlayerTests(FunctionalTestCase):
    """Tests for managing player actions during a game"""
    def test_clicking_player_opens_player_detail_section(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        homepage = game.Homepage(self.browser)
        homepage.start_button.click()

        self.story('She adds two players')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()
        add_player = game.AddPlayerPage(self.browser)
        add_player.name.clear()
        add_player.name.send_keys('Alice\n')

        game_page.add_player_link.click()
        add_player.name.clear()
        add_player.name.send_keys('Bob\n')

        self.story('Check that the two players are in the list')
        self.assertCountEqual(['Alice', 'Bob'],
            list(player.text for player in game_page.player_name_list))

        self.story('The player detail sections are both hidden')
        players = game_page.get_players()
        for player in players:
            with self.subTest(player=player['name'].text):
                self.assertIsNone(player['detail'])

        self.story('She clicks the first player and the details appear')
        players[0]['row'].click()
        players = game_page.get_players() # Get DOM updates
        self.assertIsNotNone(players[0]['detail'])
        self.assertIsNone(players[1]['detail'])

        self.story("She clicks the second player, the first player's details"
            "disappear and the second player's appear")
        players[1]['row'].click()
        players = game_page.get_players() # Get DOM updates
        self.assertIsNone(players[0]['detail'])
        self.assertIsNotNone(players[1]['detail'])

    def test_clicking_player_closes_company_detail_section(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        homepage = game.Homepage(self.browser)
        homepage.start_button.click()

        self.story('She adds a player')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()
        add_player = game.AddPlayerPage(self.browser)
        add_player.name.send_keys('Alice\n')

        self.story('She also adds a company')
        game_page.add_company_link.click()
        add_company = game.AddCompanyPage(self.browser)
        add_company.name.send_keys('PRR\n')

        self.story('She clicks the company to open its detail section')
        player = game_page.get_players()[0]
        company = game_page.get_companies()[0]
        company['elem'].click()

        # Need to retrieve DOM updates first
        company = game_page.get_companies()[0]
        player = game_page.get_players()[0]
        self.assertIsNone(player['detail'])
        self.assertIsNotNone(company['detail'])

        self.story('When she clicks the player the company detail closes')
        player['row'].click()
        player = game_page.get_players()[0]
        company = game_page.get_companies()[0]
        self.assertIsNotNone(player['detail'])
        self.assertIsNone(company['detail'])

    def test_player_can_transfer_money_to_bank(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        homepage = game.Homepage(self.browser)
        homepage.start_button.click()

        self.story('She adds a player')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()
        add_player = game.AddPlayerPage(self.browser)
        add_player.name.send_keys('Alice')
        add_player.cash.send_keys('1000\n')

        self.story('Confirm cash amounts')
        player = game_page.get_players()[0]
        self.assertEqual(player['cash'].text, '1000')
        self.assertEqual(game_page.bank_cash.text, '11000')
        self.story("She opens the player's detail view")
        player['row'].click()
        player = game_page.get_players()[0]

        self.story('There is a form that allows her to send money')
        transfer_form = game.TransferForm(self.browser)
        transfer_form.amount(player['detail']).clear()
        transfer_form.amount(player['detail']).send_keys('100')
        for radio in transfer_form.target(player['detail']):
            if radio.get_attribute('value') == 'bank':
                radio.click()
                break
        transfer_form.transfer_button(player['detail']).click()

        self.story("The page reloads and the player's cash amount has lowered")
        player = game_page.get_players()[0]
        self.assertEqual(player['cash'].text, '900')
        self.assertEqual(game_page.bank_cash.text, '11100')

        self.story('Alice goes to transfer money again')
        player['row'].click()
        player = game_page.get_players()[0] # Get DOM updates
        transfer_form.amount(player['detail']).clear()
        transfer_form.amount(player['detail']).send_keys('10')
        self.story("This time she doesn't select a target, the bank should be"
            ' default')
        transfer_form.transfer_button(player['detail']).click()

        self.story('After the page reloads she has found that the amount has'
            'changed again')
        player = game_page.get_players()[0]
        self.assertEqual(player['cash'].text, '890')
        self.assertEqual(game_page.bank_cash.text, '11110')

    def test_player_can_transfer_money_to_company(self):
        self.story('Alice is a user who starts a new game')
        self.browser.get(self.live_server_url)
        homepage = game.Homepage(self.browser)
        homepage.start_button.click()

        self.story('She adds a player')
        game_page = game.GamePage(self.browser)
        game_page.add_player_link.click()
        add_player = game.AddPlayerPage(self.browser)
        add_player.name.send_keys('Alice')
        add_player.cash.send_keys('1000\n')

        self.story('She adds a company')
        game_page.add_company_link.click()
        add_company = game.AddCompanyPage(self.browser)
        add_company.name.send_keys('CPR')
        add_company.cash.send_keys('500\n')

        self.story('Confirm cash amounts')
        player = game_page.get_players()[0]
        company = game_page.get_companies()[0]
        self.assertEqual(player['cash'].text, '1000')
        self.assertEqual(company['cash'].text, '500')

        self.story("She opens the player's detail view")
        player['row'].click()
        player = game_page.get_players()[0]

        self.story('The form allows her to transfer funds to the CPR')
        transfer_form = game.TransferForm(self.browser)
        transfer_form.amount(player['detail']).clear()
        transfer_form.amount(player['detail']).send_keys('19')
        for radio in transfer_form.target(player['detail']):
            if radio.get_attribute('id') == 'target-CPR':
                radio.click()
                break
        else:
            self.fail('No company called CPR found in transfer form')
        transfer_form.transfer_button(player['detail']).click()

        self.story('The Page reloads and money has changed hands')
        player = game_page.get_players()[0]
        company = game_page.get_companies()[0]
        self.assertEqual(player['cash'].text, '981')
        self.assertEqual(company['cash'].text, '519')
<|endoftext|>


<commit_before>import collections
from contextlib import contextmanager
import functools
import sys
import traceback
import weakref

import pytest

from pytestqt.qt_compat import QtCore, QtTest, QApplication, QT_API, \
    qInstallMsgHandler, QtDebugMsg, QtWarningMsg, QtCriticalMsg, QtFatalMsg

def _inject_qtest_methods(cls):
    """
    Injects QTest methods into the given class QtBot, so the user can access
    them directly without having to import QTest.
    """

    def create_qtest_proxy_method(method_name):

        if hasattr(QtTest.QTest, method_name):
            qtest_method = getattr(QtTest.QTest, method_name)

            def result(*args, **kwargs):
                return qtest_method(*args, **kwargs)

            functools.update_wrapper(result, qtest_method)
            return staticmethod(result)
        else:
            return None  # pragma: no cover

    # inject methods from QTest into QtBot
    method_names = [
        'keyPress',
        'keyClick',
        'keyClicks',
        'keyEvent',
        'keyPress',
        'keyRelease',
        'keyToAscii',

        'mouseClick',
        'mouseDClick',
        'mouseEvent',
        'mouseMove',
        'mousePress',
        'mouseRelease',
    ]
    for method_name in method_names:
        method = create_qtest_proxy_method(method_name)
        if method is not None:
            setattr(cls, method_name, method)

    return cls

@_inject_qtest_methods
class QtBot(object):

    """
    Instances of this class are responsible for sending events to `Qt` objects (usually widgets),
    simulating user input.

    .. important:: Instances of this class should be accessed only by using a ``qtbot`` fixture,
                    never instantiated directly.

    **Widgets**

    .. automethod:: addWidget
    .. automethod:: waitForWindowShown
    .. automethod:: stopForInteraction

    **Signals**

    .. automethod:: waitSignal

    **Raw QTest API**

    Methods below provide very low level functions, as sending a single mouse click or a key event.
    Those methods are just forwarded directly to the `QTest API`_. Consult the documentation for more
    information.

    ---

    Below are methods used to simulate sending key events to widgets:

    .. staticmethod:: keyPress(widget, key[, modifier=Qt.NoModifier[, delay=-1]])
    .. staticmethod:: keyClick (widget, key[, modifier=Qt.NoModifier[, delay=-1]])
    .. staticmethod:: keyClicks (widget, key sequence[, modifier=Qt.NoModifier[, delay=-1]])
    .. staticmethod:: keyEvent (action, widget, key[, modifier=Qt.NoModifier[, delay=-1]])
    .. staticmethod:: keyPress (widget, key[, modifier=Qt.NoModifier[, delay=-1]])
    .. staticmethod:: keyRelease (widget, key[, modifier=Qt.NoModifier[, delay=-1]])

        Sends one or more keyword events to a widget.

        :param QWidget widget: the widget that will receive the event

        :param str|int key: key to send, it can be either a Qt.Key_* constant or a single character string.

        .. _keyboard modifiers:

        :param Qt.KeyboardModifier modifier: flags OR'ed together representing other modifier keys
            also pressed. Possible flags are:

            * ``Qt.NoModifier``: No modifier key is pressed.
            * ``Qt.ShiftModifier``: A Shift key on the keyboard is pressed.
            * ``Qt.ControlModifier``: A Ctrl key on the keyboard is pressed.
            * ``Qt.AltModifier``: An Alt key on the keyboard is pressed.
            * ``Qt.MetaModifier``: A Meta key on the keyboard is pressed.
            * ``Qt.KeypadModifier``: A keypad button is pressed.
            * ``Qt.GroupSwitchModifier``: X11 only. A Mode_switch key on the keyboard is pressed.

        :param int delay: after the event, delay the test for this miliseconds (if > 0).

    .. staticmethod:: keyToAscii (key)

        Auxilliary method that converts the given constant ot its equivalent ascii.

        :param Qt.Key_* key: one of the constants for keys in the Qt namespace.

        :return type: str
        :returns: the equivalent character string.

        .. note:: this method is not available in PyQt.

    ---

    Below are methods used to simulate sending mouse events to widgets.

    .. staticmethod:: mouseClick (widget, button[, stateKey=0[, pos=QPoint()[, delay=-1]]])
    .. staticmethod:: mouseDClick (widget, button[, stateKey=0[, pos=QPoint()[, delay=-1]]])
    .. staticmethod:: mouseEvent (action, widget, button, stateKey, pos[, delay=-1])
    .. staticmethod:: mouseMove (widget[, pos=QPoint()[, delay=-1]])
    .. staticmethod:: mousePress (widget, button[, stateKey=0[, pos=QPoint()[, delay=-1]]])
    .. staticmethod:: mouseRelease (widget, button[, stateKey=0[, pos=QPoint()[, delay=-1]]])

        Sends a mouse moves and clicks to a widget.

        :param QWidget widget: the widget that will receive the event

        :param Qt.MouseButton button: flags OR'ed together representing the button pressed.
            Possible flags are:

            * ``Qt.NoButton``: The button state does not refer to any button (see QMouseEvent.button()).
            * ``Qt.LeftButton``: The left button is pressed, or an event refers to the left button. (The left button may be the right button on left-handed mice.)
            * ``Qt.RightButton``: The right button.
            * ``Qt.MidButton``: The middle button.
            * ``Qt.MiddleButton``: The middle button.
            * ``Qt.XButton1``: The first X button.
            * ``Qt.XButton2``: The second X button.

        :param Qt.KeyboardModifier modifier: flags OR'ed together representing other modifier keys
            also pressed. See `keyboard modifiers`_.

        :param QPoint position: position of the mouse pointer.

        :param int delay: after the event, delay the test for this miliseconds (if > 0).

    .. _QTest API: http://doc.qt.digia.com/4.8/qtest.html

    """

    def __init__(self, app):
        """
        :param QApplication app:
            The current QApplication instance.
        """
        self._app = app
        self._widgets = []  # list of weakref to QWidget instances

    def _close(self):
        """
        Clear up method. Called at the end of each test that uses a ``qtbot`` fixture.
        """
        for w in self._widgets:
            w = w()
            if w is not None:
                w.close()
        self._widgets[:] = []

    def addWidget(self, widget):
        """
        Adds a widget to be tracked by this bot. This is not required, but will ensure that the
        widget gets closed by the end of the test, so it is highly recommended.

        :param QWidget widget:
            Widget to keep track of.
        """
        self._widgets.append(weakref.ref(widget))

    add_widget = addWidget  # pep-8 alias

    def waitForWindowShown(self, widget):
        """
        Waits until the window is shown in the screen. This is mainly useful for asynchronous
        systems like X11, where a window will be mapped to screen some time after being asked to
        show itself on the screen.

        :param QWidget widget:
            Widget to wait on.

        .. note:: In Qt5, the actual method called is qWaitForWindowExposed,
            but this name is kept for backward compatibility
        """
        if hasattr(QtTest.QTest, 'qWaitForWindowShown'): # pragma: no cover
            # PyQt4 and PySide
            QtTest.QTest.qWaitForWindowShown(widget)
        else: # pragma: no cover
            # PyQt5
            QtTest.QTest.qWaitForWindowExposed(widget)

    wait_for_window_shown = waitForWindowShown  # pep-8 alias

    def stopForInteraction(self):
        """
        Stops the current test flow, letting the user interact with any visible widget.

        This is mainly useful so that you can verify the current state of the program while writing
        tests.

        Closing the windows should resume the test run, with ``qtbot`` attempting to restore visibility
        of the widgets as they were before this call.

        .. note:: As a convenience, it is also aliased as `stop`.
        """
        widget_and_visibility = []
        for weak_widget in self._widgets:
            widget = weak_widget()
            if widget is not None:
                widget_and_visibility.append((widget, widget.isVisible()))

        self._app.exec_()

        for widget, visible in widget_and_visibility:
            widget.setVisible(visible)

    stop = stopForInteraction

    def waitSignal(self, signal=None, timeout=1000):
        """
        Stops current test until a signal is triggered.

        Used to stop the control flow of a test until a signal is emitted, or
        a number of milliseconds, specified by ``timeout``, has elapsed.

        Best used as a context manager::

           with qtbot.waitSignal(signal, timeout=1000):
               long_function_that_calls_signal()

        Also, you can use the :class:`SignalBlocker` directly if the context
        manager form is not convenient::

           blocker = qtbot.waitSignal(signal, timeout=1000)
           blocker.connect(other_signal)
           long_function_that_calls_signal()
           blocker.wait()

        :param Signal signal:
            A signal to wait for. Set to ``None`` to just use timeout.
        :param int timeout:
            How many milliseconds to wait before resuming control flow.
        :returns:
            ``SignalBlocker`` object. Call ``SignalBlocker.wait()`` to wait.

        .. note::
           Cannot have both ``signals`` and ``timeout`` equal ``None``, or
           else you will block indefinitely. We throw an error if this occurs.

        """
        blocker = SignalBlocker(timeout=timeout)
        if signal is not None:
            blocker.connect(signal)
        return blocker

    wait_signal = waitSignal  # pep-8 alias

class SignalBlocker(object):

    """
    Returned by :meth:`QtBot.waitSignal` method.

    .. automethod:: wait
    .. automethod:: connect

    :ivar int timeout: maximum time to wait for a signal to be triggered. Can
        be changed before :meth:`wait` is called.

    :ivar bool signal_triggered: set to ``True`` if a signal was triggered, or
        ``False`` if timeout was reached instead. Until :meth:`wait` is called,
        this is set to ``None``.
    """

    def __init__(self, timeout=1000):
        self._loop = QtCore.QEventLoop()
        self._signals = []
        self.timeout = timeout
        self.signal_triggered = False

    def wait(self):
        """
        Waits until either a connected signal is triggered or timeout is reached.

        :raise ValueError: if no signals are connected and timeout is None; in
            this case it would wait forever.
        """
        if self.signal_triggered:
            return
        if self.timeout is None and len(self._signals) == 0:
            raise ValueError("No signals or timeout specified.")
        if self.timeout is not None:
            QtCore.QTimer.singleShot(self.timeout, self._loop.quit)
        self._loop.exec_()

    def connect(self, signal):
        """
        Connects to the given signal, making :meth:`wait()` return once this signal
        is emitted.

        :param signal: QtCore.Signal
        """
        signal.connect(self._quit_loop_by_signal)
        self._signals.append(signal)

    def _quit_loop_by_signal(self):
        """
        quits the event loop and marks that we finished because of a signal.
        """
        self.signal_triggered = True
        self._loop.quit()

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.wait()

@contextmanager
def capture_exceptions():
    """
    Context manager that captures exceptions that happen insides its context,
    and returns them as a list of (type, value, traceback) after the
    context ends.
    """
    result = []

    def hook(type_, value, tback):
        result.append((type_, value, tback))
        sys.__excepthook__(type_, value, tback)

    sys.excepthook = hook
    try:
        yield result
    finally:
        sys.excepthook = sys.__excepthook__

def format_captured_exceptions(exceptions):
    """
    Formats exceptions given as (type, value, traceback) into a string
    suitable to display as a test failure.
    """
    message = 'Qt exceptions in virtual methods:\n'
    message += '_' * 80 + '\n'
    for (exc_type, value, tback) in exceptions:
        message += ''.join(traceback.format_tb(tback)) + '\n'
        message += '%s: %s\n' % (exc_type.__name__, value)
        message += '_' * 80 + '\n'
    return message

@pytest.yield_fixture(scope='session')
def qapp():
    """
    fixture that instantiates the QApplication instance that will be used by
    the tests.
    """
    app = QApplication.instance()
    if app is None:
        app = QApplication([])
        yield app
        app.exit()
        app.deleteLater()
    else:
        yield app  # pragma: no cover

@pytest.yield_fixture
def qtbot(qapp, request):
    """
    Fixture used to create a QtBot instance for using during testing.

    Make sure to call addWidget for each top-level widget you create to ensure
    that they are properly closed after the test ends.
    """
    result = QtBot(qapp)
    no_capture = request.node.get_marker('qt_no_exception_capture') or \
                 request.config.getini('qt_no_exception_capture')
    if no_capture:
        yield result  # pragma: no cover
    else:
        with capture_exceptions() as exceptions:
            yield result
        if exceptions:
            pytest.fail(format_captured_exceptions(exceptions))

    result._close()

def pytest_addoption(parser):
    parser.addini('qt_no_exception_capture',
                  'disable automatic exception capture')

    parser.addoption('--no-qt-log', dest='qt_log', action='store_false',
                     default=True)

def pytest_configure(config):
    config.addinivalue_line(
        'markers',
        "qt_no_exception_capture: Disables pytest-qt's automatic exception "
        'capture for just one test item.')

    if config.getoption('qt_log'):
        config.pluginmanager.register(QtLoggingPlugin(config), '_qt_logging')

def pytest_report_header():
    return ['qt-api: %s' % QT_API]

class QtLoggingPlugin(object):
    """
    Pluging responsible for installing a QtMessageHandler before each
    test and augment reporting if the test failed with the messages captured.
    """

    def __init__(self, config):
        self.config = config

    def pytest_runtest_setup(self, item):
        item.qt_log_handler = _QtMessageCapture()
        previous_handler = qInstallMsgHandler(item.qt_log_handler.handle)
        item.qt_previous_handler = previous_handler

    @pytest.mark.hookwrapper
    def pytest_runtest_makereport(self, item, call):
        """Add captured Qt messages to test item report if the call failed."""

        outcome = yield
        report = outcome.result

        if call.when == 'call':

            if not report.passed:
                long_repr = getattr(report, 'longrepr', None)
                if hasattr(long_repr, 'addsection'):
                    lines = []
                    for msg_type, msg in item.qt_log_handler.messages:
                        msg_name = _QtMessageCapture.get_msg_name(msg_type)
                        lines.append('{0}: {1}'.format(msg_name, msg))
                    if lines:
                        long_repr.addsection('Captured Qt messages',
                                             '\n'.join(lines))
            # Release the handler resources.
            qInstallMsgHandler(item.qt_previous_handler)
            del item.qt_previous_handler
            del item.qt_log_handler

class _QtMessageCapture(object):
    """
    Captures Qt messages when its `handle` method is installed using
    qInstallMsgHandler, and stores them into `messages` attribute.

    :attr messages: list of Message named-tuples.
    """

    Message = collections.namedtuple('Message', 'msg_type, msg')

    def __init__(self):
        self.messages = []

    def handle(self, msg_type, msg):
        """
        Method to be installed using qInstallMsgHandler, stores each message
        into the `messages` attribute.
        """
        if type(msg) is bytes:
            msg = msg.decode('utf-8', errors='replace')
        self.messages.append(self.Message(msg_type, msg))

    @classmethod
    def get_msg_name(cls, msg_type):
        """Returns a string representation of the given QtMsgType enum value."""
        return {
            QtDebugMsg: 'QtDebugMsg',
            QtWarningMsg: 'QtWarningMsg',
            QtCriticalMsg: 'QtCriticalMsg',
            QtFatalMsg: 'QtFatalMsg',
        }[msg_type]
<commit_msg>Use isinstance instead of type(), as suggested by @The-Compiler<commit_after>import collections
from contextlib import contextmanager
import functools
import sys
import traceback
import weakref

import pytest

from pytestqt.qt_compat import QtCore, QtTest, QApplication, QT_API, \
    qInstallMsgHandler, QtDebugMsg, QtWarningMsg, QtCriticalMsg, QtFatalMsg

def _inject_qtest_methods(cls):
    """
    Injects QTest methods into the given class QtBot, so the user can access
    them directly without having to import QTest.
    """

    def create_qtest_proxy_method(method_name):

        if hasattr(QtTest.QTest, method_name):
            qtest_method = getattr(QtTest.QTest, method_name)

            def result(*args, **kwargs):
                return qtest_method(*args, **kwargs)

            functools.update_wrapper(result, qtest_method)
            return staticmethod(result)
        else:
            return None  # pragma: no cover

    # inject methods from QTest into QtBot
    method_names = [
        'keyPress',
        'keyClick',
        'keyClicks',
        'keyEvent',
        'keyPress',
        'keyRelease',
        'keyToAscii',

        'mouseClick',
        'mouseDClick',
        'mouseEvent',
        'mouseMove',
        'mousePress',
        'mouseRelease',
    ]
    for method_name in method_names:
        method = create_qtest_proxy_method(method_name)
        if method is not None:
            setattr(cls, method_name, method)

    return cls

@_inject_qtest_methods
class QtBot(object):

    """
    Instances of this class are responsible for sending events to `Qt` objects (usually widgets),
    simulating user input.

    .. important:: Instances of this class should be accessed only by using a ``qtbot`` fixture,
                    never instantiated directly.

    **Widgets**

    .. automethod:: addWidget
    .. automethod:: waitForWindowShown
    .. automethod:: stopForInteraction

    **Signals**

    .. automethod:: waitSignal

    **Raw QTest API**

    Methods below provide very low level functions, as sending a single mouse click or a key event.
    Those methods are just forwarded directly to the `QTest API`_. Consult the documentation for more
    information.

    ---

    Below are methods used to simulate sending key events to widgets:

    .. staticmethod:: keyPress(widget, key[, modifier=Qt.NoModifier[, delay=-1]])
    .. staticmethod:: keyClick (widget, key[, modifier=Qt.NoModifier[, delay=-1]])
    .. staticmethod:: keyClicks (widget, key sequence[, modifier=Qt.NoModifier[, delay=-1]])
    .. staticmethod:: keyEvent (action, widget, key[, modifier=Qt.NoModifier[, delay=-1]])
    .. staticmethod:: keyPress (widget, key[, modifier=Qt.NoModifier[, delay=-1]])
    .. staticmethod:: keyRelease (widget, key[, modifier=Qt.NoModifier[, delay=-1]])

        Sends one or more keyword events to a widget.

        :param QWidget widget: the widget that will receive the event

        :param str|int key: key to send, it can be either a Qt.Key_* constant or a single character string.

        .. _keyboard modifiers:

        :param Qt.KeyboardModifier modifier: flags OR'ed together representing other modifier keys
            also pressed. Possible flags are:

            * ``Qt.NoModifier``: No modifier key is pressed.
            * ``Qt.ShiftModifier``: A Shift key on the keyboard is pressed.
            * ``Qt.ControlModifier``: A Ctrl key on the keyboard is pressed.
            * ``Qt.AltModifier``: An Alt key on the keyboard is pressed.
            * ``Qt.MetaModifier``: A Meta key on the keyboard is pressed.
            * ``Qt.KeypadModifier``: A keypad button is pressed.
            * ``Qt.GroupSwitchModifier``: X11 only. A Mode_switch key on the keyboard is pressed.

        :param int delay: after the event, delay the test for this miliseconds (if > 0).

    .. staticmethod:: keyToAscii (key)

        Auxilliary method that converts the given constant ot its equivalent ascii.

        :param Qt.Key_* key: one of the constants for keys in the Qt namespace.

        :return type: str
        :returns: the equivalent character string.

        .. note:: this method is not available in PyQt.

    ---

    Below are methods used to simulate sending mouse events to widgets.

    .. staticmethod:: mouseClick (widget, button[, stateKey=0[, pos=QPoint()[, delay=-1]]])
    .. staticmethod:: mouseDClick (widget, button[, stateKey=0[, pos=QPoint()[, delay=-1]]])
    .. staticmethod:: mouseEvent (action, widget, button, stateKey, pos[, delay=-1])
    .. staticmethod:: mouseMove (widget[, pos=QPoint()[, delay=-1]])
    .. staticmethod:: mousePress (widget, button[, stateKey=0[, pos=QPoint()[, delay=-1]]])
    .. staticmethod:: mouseRelease (widget, button[, stateKey=0[, pos=QPoint()[, delay=-1]]])

        Sends a mouse moves and clicks to a widget.

        :param QWidget widget: the widget that will receive the event

        :param Qt.MouseButton button: flags OR'ed together representing the button pressed.
            Possible flags are:

            * ``Qt.NoButton``: The button state does not refer to any button (see QMouseEvent.button()).
            * ``Qt.LeftButton``: The left button is pressed, or an event refers to the left button. (The left button may be the right button on left-handed mice.)
            * ``Qt.RightButton``: The right button.
            * ``Qt.MidButton``: The middle button.
            * ``Qt.MiddleButton``: The middle button.
            * ``Qt.XButton1``: The first X button.
            * ``Qt.XButton2``: The second X button.

        :param Qt.KeyboardModifier modifier: flags OR'ed together representing other modifier keys
            also pressed. See `keyboard modifiers`_.

        :param QPoint position: position of the mouse pointer.

        :param int delay: after the event, delay the test for this miliseconds (if > 0).

    .. _QTest API: http://doc.qt.digia.com/4.8/qtest.html

    """

    def __init__(self, app):
        """
        :param QApplication app:
            The current QApplication instance.
        """
        self._app = app
        self._widgets = []  # list of weakref to QWidget instances

    def _close(self):
        """
        Clear up method. Called at the end of each test that uses a ``qtbot`` fixture.
        """
        for w in self._widgets:
            w = w()
            if w is not None:
                w.close()
        self._widgets[:] = []

    def addWidget(self, widget):
        """
        Adds a widget to be tracked by this bot. This is not required, but will ensure that the
        widget gets closed by the end of the test, so it is highly recommended.

        :param QWidget widget:
            Widget to keep track of.
        """
        self._widgets.append(weakref.ref(widget))

    add_widget = addWidget  # pep-8 alias

    def waitForWindowShown(self, widget):
        """
        Waits until the window is shown in the screen. This is mainly useful for asynchronous
        systems like X11, where a window will be mapped to screen some time after being asked to
        show itself on the screen.

        :param QWidget widget:
            Widget to wait on.

        .. note:: In Qt5, the actual method called is qWaitForWindowExposed,
            but this name is kept for backward compatibility
        """
        if hasattr(QtTest.QTest, 'qWaitForWindowShown'): # pragma: no cover
            # PyQt4 and PySide
            QtTest.QTest.qWaitForWindowShown(widget)
        else: # pragma: no cover
            # PyQt5
            QtTest.QTest.qWaitForWindowExposed(widget)

    wait_for_window_shown = waitForWindowShown  # pep-8 alias

    def stopForInteraction(self):
        """
        Stops the current test flow, letting the user interact with any visible widget.

        This is mainly useful so that you can verify the current state of the program while writing
        tests.

        Closing the windows should resume the test run, with ``qtbot`` attempting to restore visibility
        of the widgets as they were before this call.

        .. note:: As a convenience, it is also aliased as `stop`.
        """
        widget_and_visibility = []
        for weak_widget in self._widgets:
            widget = weak_widget()
            if widget is not None:
                widget_and_visibility.append((widget, widget.isVisible()))

        self._app.exec_()

        for widget, visible in widget_and_visibility:
            widget.setVisible(visible)

    stop = stopForInteraction

    def waitSignal(self, signal=None, timeout=1000):
        """
        Stops current test until a signal is triggered.

        Used to stop the control flow of a test until a signal is emitted, or
        a number of milliseconds, specified by ``timeout``, has elapsed.

        Best used as a context manager::

           with qtbot.waitSignal(signal, timeout=1000):
               long_function_that_calls_signal()

        Also, you can use the :class:`SignalBlocker` directly if the context
        manager form is not convenient::

           blocker = qtbot.waitSignal(signal, timeout=1000)
           blocker.connect(other_signal)
           long_function_that_calls_signal()
           blocker.wait()

        :param Signal signal:
            A signal to wait for. Set to ``None`` to just use timeout.
        :param int timeout:
            How many milliseconds to wait before resuming control flow.
        :returns:
            ``SignalBlocker`` object. Call ``SignalBlocker.wait()`` to wait.

        .. note::
           Cannot have both ``signals`` and ``timeout`` equal ``None``, or
           else you will block indefinitely. We throw an error if this occurs.

        """
        blocker = SignalBlocker(timeout=timeout)
        if signal is not None:
            blocker.connect(signal)
        return blocker

    wait_signal = waitSignal  # pep-8 alias

class SignalBlocker(object):

    """
    Returned by :meth:`QtBot.waitSignal` method.

    .. automethod:: wait
    .. automethod:: connect

    :ivar int timeout: maximum time to wait for a signal to be triggered. Can
        be changed before :meth:`wait` is called.

    :ivar bool signal_triggered: set to ``True`` if a signal was triggered, or
        ``False`` if timeout was reached instead. Until :meth:`wait` is called,
        this is set to ``None``.
    """

    def __init__(self, timeout=1000):
        self._loop = QtCore.QEventLoop()
        self._signals = []
        self.timeout = timeout
        self.signal_triggered = False

    def wait(self):
        """
        Waits until either a connected signal is triggered or timeout is reached.

        :raise ValueError: if no signals are connected and timeout is None; in
            this case it would wait forever.
        """
        if self.signal_triggered:
            return
        if self.timeout is None and len(self._signals) == 0:
            raise ValueError("No signals or timeout specified.")
        if self.timeout is not None:
            QtCore.QTimer.singleShot(self.timeout, self._loop.quit)
        self._loop.exec_()

    def connect(self, signal):
        """
        Connects to the given signal, making :meth:`wait()` return once this signal
        is emitted.

        :param signal: QtCore.Signal
        """
        signal.connect(self._quit_loop_by_signal)
        self._signals.append(signal)

    def _quit_loop_by_signal(self):
        """
        quits the event loop and marks that we finished because of a signal.
        """
        self.signal_triggered = True
        self._loop.quit()

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.wait()

@contextmanager
def capture_exceptions():
    """
    Context manager that captures exceptions that happen insides its context,
    and returns them as a list of (type, value, traceback) after the
    context ends.
    """
    result = []

    def hook(type_, value, tback):
        result.append((type_, value, tback))
        sys.__excepthook__(type_, value, tback)

    sys.excepthook = hook
    try:
        yield result
    finally:
        sys.excepthook = sys.__excepthook__

def format_captured_exceptions(exceptions):
    """
    Formats exceptions given as (type, value, traceback) into a string
    suitable to display as a test failure.
    """
    message = 'Qt exceptions in virtual methods:\n'
    message += '_' * 80 + '\n'
    for (exc_type, value, tback) in exceptions:
        message += ''.join(traceback.format_tb(tback)) + '\n'
        message += '%s: %s\n' % (exc_type.__name__, value)
        message += '_' * 80 + '\n'
    return message

@pytest.yield_fixture(scope='session')
def qapp():
    """
    fixture that instantiates the QApplication instance that will be used by
    the tests.
    """
    app = QApplication.instance()
    if app is None:
        app = QApplication([])
        yield app
        app.exit()
        app.deleteLater()
    else:
        yield app  # pragma: no cover

@pytest.yield_fixture
def qtbot(qapp, request):
    """
    Fixture used to create a QtBot instance for using during testing.

    Make sure to call addWidget for each top-level widget you create to ensure
    that they are properly closed after the test ends.
    """
    result = QtBot(qapp)
    no_capture = request.node.get_marker('qt_no_exception_capture') or \
                 request.config.getini('qt_no_exception_capture')
    if no_capture:
        yield result  # pragma: no cover
    else:
        with capture_exceptions() as exceptions:
            yield result
        if exceptions:
            pytest.fail(format_captured_exceptions(exceptions))

    result._close()

def pytest_addoption(parser):
    parser.addini('qt_no_exception_capture',
                  'disable automatic exception capture')

    parser.addoption('--no-qt-log', dest='qt_log', action='store_false',
                     default=True)

def pytest_configure(config):
    config.addinivalue_line(
        'markers',
        "qt_no_exception_capture: Disables pytest-qt's automatic exception "
        'capture for just one test item.')

    if config.getoption('qt_log'):
        config.pluginmanager.register(QtLoggingPlugin(config), '_qt_logging')

def pytest_report_header():
    return ['qt-api: %s' % QT_API]

class QtLoggingPlugin(object):
    """
    Pluging responsible for installing a QtMessageHandler before each
    test and augment reporting if the test failed with the messages captured.
    """

    def __init__(self, config):
        self.config = config

    def pytest_runtest_setup(self, item):
        item.qt_log_handler = _QtMessageCapture()
        previous_handler = qInstallMsgHandler(item.qt_log_handler.handle)
        item.qt_previous_handler = previous_handler

    @pytest.mark.hookwrapper
    def pytest_runtest_makereport(self, item, call):
        """Add captured Qt messages to test item report if the call failed."""

        outcome = yield
        report = outcome.result

        if call.when == 'call':

            if not report.passed:
                long_repr = getattr(report, 'longrepr', None)
                if hasattr(long_repr, 'addsection'):
                    lines = []
                    for msg_type, msg in item.qt_log_handler.messages:
                        msg_name = _QtMessageCapture.get_msg_name(msg_type)
                        lines.append('{0}: {1}'.format(msg_name, msg))
                    if lines:
                        long_repr.addsection('Captured Qt messages',
                                             '\n'.join(lines))
            # Release the handler resources.
            qInstallMsgHandler(item.qt_previous_handler)
            del item.qt_previous_handler
            del item.qt_log_handler

class _QtMessageCapture(object):
    """
    Captures Qt messages when its `handle` method is installed using
    qInstallMsgHandler, and stores them into `messages` attribute.

    :attr messages: list of Message named-tuples.
    """

    Message = collections.namedtuple('Message', 'msg_type, msg')

    def __init__(self):
        self.messages = []

    def handle(self, msg_type, msg):
        """
        Method to be installed using qInstallMsgHandler, stores each message
        into the `messages` attribute.
        """
        if isinstance(msg, bytes):
            msg = msg.decode('utf-8', errors='replace')
        self.messages.append(self.Message(msg_type, msg))

    @classmethod
    def get_msg_name(cls, msg_type):
        """Returns a string representation of the given QtMsgType enum value."""
        return {
            QtDebugMsg: 'QtDebugMsg',
            QtWarningMsg: 'QtWarningMsg',
            QtCriticalMsg: 'QtCriticalMsg',
            QtFatalMsg: 'QtFatalMsg',
        }[msg_type]
<|endoftext|>


<commit_before>#!/usr/bin/env python

# This file is part of VoltDB.
# Copyright (C) 2008-2015 VoltDB Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.

# HACK:
# This SQL coverage configuration set represents hopefully the largest
# set of statements that should always pass.  Some of the template files
# generate statements that result in repeated failures due to functional
# differences between HSQL and VoltDB backends. In such cases, we work around
# these errors by generating a fixed sample query file and, after culling out
# any statements that cause mismatches, using it to replace the original
# template file name in the configuration list below.
# In this way, the sample file gets used as a trivial template file that passes
# through the generator untouched. It also has an unfortunate side-effect of
# causing any future improvements to the template to be ignored unless/until
# the sample file is manually re-generated from it and re-edited to eliminate
# mismatches.
#
# Actually, in the specific case of templates that generate random integer
# constant timestamp values, the template must be replaced by TWO separate
# generated sample files -- one generated for hsql with millisecond constants
# and one for VoltDB with microsecond constants (always 1000 X the hsql values).
# The hsql version of the sample file gets associated with the optional
# "template-hsqldb" key in the configuration. Otherwise, both hsql and VoltDB
# use the same input file associated with the "template" key.
#
# The generated sample files follow a naming convention of starting with
# "regression". The hsql variants end in "-hsql.sql".
# It is NOT advisable to try to edit these sample files directly.
# It is better to edit the original template, re-generate the sample(s),
# and re-cull the resulting "mismatches" -- being careful to cull ONLY
# mismatches that are NOT accountable to the known backend differences that
# we are working around in this way. It helps to have comments, below,
# to describe which specific issues each "regression" file is intended to
# work around.
#
# To regenerate the regression-*.sql file for a configuration, run the SQLGenerateReport.py
# tool on the report.xml file generated for that configuration, using the -f true switch,
# which will cause the successful statements to be written to stdout.
{
# THESE ALL SUCCEED, USE THE TEMPLATE INPUT
    "basic": {"schema": "schema.py",
              "ddl": "DDL.sql",
              "template": "basic.sql",
              "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE THE TEMPLATE INPUT
    "basic-joins": {"schema": "schema.py",
                    "ddl": "DDL.sql",
                    "template": "basic-joins.sql",
                    "normalizer": "normalizer.py"},
    "basic-int-joins": {"schema": "int-schema.py",
                    "ddl": "int-DDL.sql",
                    "template": "basic-int-joins.sql",
                    "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE THE TEMPLATE INPUT
    "basic-unions": {"schema": "union-schema.py",
                     "ddl": "DDL.sql",
                     "template": "basic-unions.sql",
                     "normalizer": "normalizer.py"},
    "mixed-unions": {"schema": "union-schema.py",
                     "ddl": "DDL.sql",
                     "template": "mixed-unions.sql",
                     "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE THE TEMPLATE INPUT
    "basic-index": {"schema": "schema.py",
                    "ddl": "index-DDL.sql",
                    "template": "basic.sql",
                    "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE THE TEMPLATE INPUT
    "basic-strings": {"schema": "strings-schema.py",
                      "ddl": "strings-DDL.sql",
                      "template": "basic-strings.sql",
                      "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE TEMPLATE INPUT
    "partial-covering": {"schema": "partial-covering-schema.py",
                         "ddl": "partial-covering-DDL.sql",
                         "template": "partial-covering.sql",
                         "normalizer": "normalizer.py"},
# BIGINT OVERFLOW CAUSES FAILURES IN THIS SUITE, USE REGRESSION INPUT
    "regression-basic-ints": {"schema": "int-schema.py",
                              "ddl": "int-DDL.sql",
                              "template": "regression-basic-ints.sql",
                              "normalizer": "normalizer.py"},
# HSQL HAS BAD DEFAULT PRECISION
# AND VoltDB gives VOLTDB ERROR: Type DECIMAL can't be cast as FLOAT
# AND HSQLDB backend gives the likes of:
#   VOLTDB ERROR: UNEXPECTED FAILURE: org.voltdb.ExpectedProcedureException:
#   HSQLDB Backend DML Error (Scale of 56.11063569750000000000000000000000 is 32 and the max is 12)
# USE REGRESSION INPUT
    "regression-basic-decimal": {"schema": "decimal-schema.py",
                                 "ddl": "DDL.sql",
                                 "template": "regression-basic-decimal.sql",
                                 "normalizer": "normalizer.py"},
# If the ONLY problem was that HSQL HAS BAD DEFAULT PRECISION, we could use the original template input
# and FUZZY MATCHING, instead.
# Enable this test to investigate the "DECIMAL can't be cast as FLOAT" and/or "Backend DML Error" issues
# without being thrown off by HSQL HAS BAD DEFAULT PRECISION issues.
#    "basic-decimal-fuzzy": {"schema": "decimal-schema.py",
#                            "ddl": "DDL.sql",
#                            "template": "basic-decimal.sql",
#                            "normalizer": "fuzzynormalizer.py"},
#
# FLOATING POINT ROUNDING ISSUES BETWEEN VOLT AND HSQL, USE FUZZY MATCHING
    "basic-timestamp": {"schema": "timestamp-schema.py",
                        "ddl": "DDL.sql",
                        "template": "basic-timestamp.sql",
                        "normalizer": "normalizer.py"},
    "advanced-timestamp": {"schema": "timestamp-schema.py",
                           "ddl": "DDL.sql",
                           "template": "advanced-timestamp.sql",
                           "normalizer": "normalizer.py"},
# BIGINT OVERFLOW CAUSES FAILURES IN THIS SUITE, USE REGRESSION INPUT
    "regression-basic-matview": {"schema": "matview-basic-schema.py",
                                 "ddl": "matview-DDL.sql",
                                 "template": "regression-basic-matview.sql",
                                 "normalizer": "normalizer.py"},

# THESE ALL SUCCEED, USE TEMPLATE INPUT
    "advanced": {"schema": "schema.py",
                 "ddl": "DDL.sql",
                 "template": "advanced.sql",
                 "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE TEMPLATE INPUT
    "advanced-index": {"schema": "schema.py",
                       "ddl": "index-DDL.sql",
                       "template": "advanced.sql",
                       "normalizer": "normalizer.py"},
    "advanced-compoundex": {"schema": "schema.py",
                       "ddl": "compoundex-DDL.sql",
                       "template": "advanced.sql",
                       "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE TEMPLATE INPUT
    "advanced-strings": {"schema": "strings-schema.py",
                         "ddl": "strings-DDL.sql",
                         "template": "advanced-strings.sql",
                         "normalizer": "normalizer.py"},
    "advanced-ints": {"schema": "int-schema.py",
                      "ddl": "int-DDL.sql",
                      "template": "advanced-ints.sql",
                      "normalizer": "normalizer.py"},
# BIGINT OVERFLOW CAUSES FAILURES IN THIS SUITE, USE REGRESSION INPUT
    "regression-advanced-ints-cntonly": {"schema": "int-schema.py",
                                         "ddl": "int-DDL.sql",
                                         "template": "regression-advanced-ints-cntonly.sql",
                                         "normalizer": "not-a-normalizer.py"},
# To test advanced IN/EXISTS
    "advanced-inexists": {"schema": "int-schema.py",
                          "ddl": "int-DDL.sql",
                          "template": "advanced-inexists.sql",
                          "normalizer": "normalizer.py"},

# ADVANCED MATERIALIZED VIEW TESTING, INCLUDING COMPLEX GROUP BY AND AGGREGATIONS.
    "advanced-matview-nonjoin": {"schema": "matview-advanced-nonjoin-schema.py",
                                 "ddl": "matview-DDL.sql",
                                 "template": "advanced-matview-nonjoin.sql",
                                 "normalizer": "normalizer.py"},

    "advanced-matview-join": {"schema": "matview-advanced-join-schema.py",
                              "ddl": "matview-DDL.sql",
                              "template": "advanced-matview-join.sql",
                              "normalizer": "normalizer.py"},

# To test index count
    "index-count1": {"schema": "index-count1-schema.py",
                     "ddl": "DDL.sql",
                     "template": "index-count1.sql",
                     "normalizer": "normalizer.py"},

# To test index scan: forward scan, reverse scan
    "index-scan": {"schema": "index-scan-schema.py",
        "ddl": "index-DDL.sql",
        "template": "index-scan.sql",
        "normalizer": "normalizer.py"},

# This suite written to test push-down of aggregates and limits in combination
# with indexes, projections and order-by.
    "pushdown": {"schema": "pushdown-schema.py",
                 "ddl": "DDL.sql",
                 "template": "pushdown.sql",
                 "normalizer": "normalizer.py"},
# HSQL SEEMS TO HAVE A BAD DEFAULT PRECISION
# AND VoltDB gives VOLTDB ERROR: Type DECIMAL can't be cast as FLOAT, so keep it disabled, for now.
# If the only problem were HSQL HAS BAD DEFAULT PRECISION, we could USE FUZZY MATCHING.
# Enable this test to investigate the "DECIMAL can't be cast as FLOAT" issue
#    "advanced-decimal-fuzzy": {"schema": "decimal-schema.py",
#                               "ddl": "DDL.sql",
#                               "template": "advanced-decimal.sql",
#                               "normalizer": "fuzzynormalizer.py"},
}

<commit_msg>regression-config.py: added a new 'advanced-scalar-subquery' test suite.<commit_after>#!/usr/bin/env python

# This file is part of VoltDB.
# Copyright (C) 2008-2015 VoltDB Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.

# HACK:
# This SQL coverage configuration set represents hopefully the largest
# set of statements that should always pass.  Some of the template files
# generate statements that result in repeated failures due to functional
# differences between HSQL and VoltDB backends. In such cases, we work around
# these errors by generating a fixed sample query file and, after culling out
# any statements that cause mismatches, using it to replace the original
# template file name in the configuration list below.
# In this way, the sample file gets used as a trivial template file that passes
# through the generator untouched. It also has an unfortunate side-effect of
# causing any future improvements to the template to be ignored unless/until
# the sample file is manually re-generated from it and re-edited to eliminate
# mismatches.
#
# Actually, in the specific case of templates that generate random integer
# constant timestamp values, the template must be replaced by TWO separate
# generated sample files -- one generated for hsql with millisecond constants
# and one for VoltDB with microsecond constants (always 1000 X the hsql values).
# The hsql version of the sample file gets associated with the optional
# "template-hsqldb" key in the configuration. Otherwise, both hsql and VoltDB
# use the same input file associated with the "template" key.
#
# The generated sample files follow a naming convention of starting with
# "regression". The hsql variants end in "-hsql.sql".
# It is NOT advisable to try to edit these sample files directly.
# It is better to edit the original template, re-generate the sample(s),
# and re-cull the resulting "mismatches" -- being careful to cull ONLY
# mismatches that are NOT accountable to the known backend differences that
# we are working around in this way. It helps to have comments, below,
# to describe which specific issues each "regression" file is intended to
# work around.
#
# To regenerate the regression-*.sql file for a configuration, run the SQLGenerateReport.py
# tool on the report.xml file generated for that configuration, using the -f true switch,
# which will cause the successful statements to be written to stdout.
{
# THESE ALL SUCCEED, USE THE TEMPLATE INPUT
    "basic": {"schema": "schema.py",
              "ddl": "DDL.sql",
              "template": "basic.sql",
              "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE THE TEMPLATE INPUT
    "basic-joins": {"schema": "schema.py",
                    "ddl": "DDL.sql",
                    "template": "basic-joins.sql",
                    "normalizer": "normalizer.py"},
    "basic-int-joins": {"schema": "int-schema.py",
                    "ddl": "int-DDL.sql",
                    "template": "basic-int-joins.sql",
                    "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE THE TEMPLATE INPUT
    "basic-unions": {"schema": "union-schema.py",
                     "ddl": "DDL.sql",
                     "template": "basic-unions.sql",
                     "normalizer": "normalizer.py"},
    "mixed-unions": {"schema": "union-schema.py",
                     "ddl": "DDL.sql",
                     "template": "mixed-unions.sql",
                     "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE THE TEMPLATE INPUT
    "basic-index": {"schema": "schema.py",
                    "ddl": "index-DDL.sql",
                    "template": "basic.sql",
                    "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE THE TEMPLATE INPUT
    "basic-strings": {"schema": "strings-schema.py",
                      "ddl": "strings-DDL.sql",
                      "template": "basic-strings.sql",
                      "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE TEMPLATE INPUT
    "partial-covering": {"schema": "partial-covering-schema.py",
                         "ddl": "partial-covering-DDL.sql",
                         "template": "partial-covering.sql",
                         "normalizer": "normalizer.py"},
# BIGINT OVERFLOW CAUSES FAILURES IN THIS SUITE, USE REGRESSION INPUT
    "regression-basic-ints": {"schema": "int-schema.py",
                              "ddl": "int-DDL.sql",
                              "template": "regression-basic-ints.sql",
                              "normalizer": "normalizer.py"},
# HSQL HAS BAD DEFAULT PRECISION
# AND VoltDB gives VOLTDB ERROR: Type DECIMAL can't be cast as FLOAT
# AND HSQLDB backend gives the likes of:
#   VOLTDB ERROR: UNEXPECTED FAILURE: org.voltdb.ExpectedProcedureException:
#   HSQLDB Backend DML Error (Scale of 56.11063569750000000000000000000000 is 32 and the max is 12)
# USE REGRESSION INPUT
    "regression-basic-decimal": {"schema": "decimal-schema.py",
                                 "ddl": "DDL.sql",
                                 "template": "regression-basic-decimal.sql",
                                 "normalizer": "normalizer.py"},
# If the ONLY problem was that HSQL HAS BAD DEFAULT PRECISION, we could use the original template input
# and FUZZY MATCHING, instead.
# Enable this test to investigate the "DECIMAL can't be cast as FLOAT" and/or "Backend DML Error" issues
# without being thrown off by HSQL HAS BAD DEFAULT PRECISION issues.
#    "basic-decimal-fuzzy": {"schema": "decimal-schema.py",
#                            "ddl": "DDL.sql",
#                            "template": "basic-decimal.sql",
#                            "normalizer": "fuzzynormalizer.py"},
#
# FLOATING POINT ROUNDING ISSUES BETWEEN VOLT AND HSQL, USE FUZZY MATCHING
    "basic-timestamp": {"schema": "timestamp-schema.py",
                        "ddl": "DDL.sql",
                        "template": "basic-timestamp.sql",
                        "normalizer": "normalizer.py"},
    "advanced-timestamp": {"schema": "timestamp-schema.py",
                           "ddl": "DDL.sql",
                           "template": "advanced-timestamp.sql",
                           "normalizer": "normalizer.py"},
# BIGINT OVERFLOW CAUSES FAILURES IN THIS SUITE, USE REGRESSION INPUT
    "regression-basic-matview": {"schema": "matview-basic-schema.py",
                                 "ddl": "matview-DDL.sql",
                                 "template": "regression-basic-matview.sql",
                                 "normalizer": "normalizer.py"},

# THESE ALL SUCCEED, USE TEMPLATE INPUT
    "advanced": {"schema": "schema.py",
                 "ddl": "DDL.sql",
                 "template": "advanced.sql",
                 "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE TEMPLATE INPUT
    "advanced-index": {"schema": "schema.py",
                       "ddl": "index-DDL.sql",
                       "template": "advanced.sql",
                       "normalizer": "normalizer.py"},
    "advanced-compoundex": {"schema": "schema.py",
                       "ddl": "compoundex-DDL.sql",
                       "template": "advanced.sql",
                       "normalizer": "normalizer.py"},
# THESE ALL SUCCEED, USE TEMPLATE INPUT
    "advanced-strings": {"schema": "strings-schema.py",
                         "ddl": "strings-DDL.sql",
                         "template": "advanced-strings.sql",
                         "normalizer": "normalizer.py"},
    "advanced-ints": {"schema": "int-schema.py",
                      "ddl": "int-DDL.sql",
                      "template": "advanced-ints.sql",
                      "normalizer": "normalizer.py"},
# BIGINT OVERFLOW CAUSES FAILURES IN THIS SUITE, USE REGRESSION INPUT
    "regression-advanced-ints-cntonly": {"schema": "int-schema.py",
                                         "ddl": "int-DDL.sql",
                                         "template": "regression-advanced-ints-cntonly.sql",
                                         "normalizer": "not-a-normalizer.py"},
# To test advanced IN/EXISTS
    "advanced-inexists": {"schema": "int-schema.py",
                          "ddl": "int-DDL.sql",
                          "template": "advanced-inexists.sql",
                          "normalizer": "normalizer.py"},

# To test advanced scalar subqueries
    "advanced-scalar-subquery": {"schema": "schema.py",
                                 "ddl": "DDL.sql",
                                 "template": "advanced-scalar-subquery.sql",
                                 "normalizer": "normalizer.py"},

# ADVANCED MATERIALIZED VIEW TESTING, INCLUDING COMPLEX GROUP BY AND AGGREGATIONS.
    "advanced-matview-nonjoin": {"schema": "matview-advanced-nonjoin-schema.py",
                                 "ddl": "matview-DDL.sql",
                                 "template": "advanced-matview-nonjoin.sql",
                                 "normalizer": "normalizer.py"},

    "advanced-matview-join": {"schema": "matview-advanced-join-schema.py",
                              "ddl": "matview-DDL.sql",
                              "template": "advanced-matview-join.sql",
                              "normalizer": "normalizer.py"},

# To test index count
    "index-count1": {"schema": "index-count1-schema.py",
                     "ddl": "DDL.sql",
                     "template": "index-count1.sql",
                     "normalizer": "normalizer.py"},

# To test index scan: forward scan, reverse scan
    "index-scan": {"schema": "index-scan-schema.py",
        "ddl": "index-DDL.sql",
        "template": "index-scan.sql",
        "normalizer": "normalizer.py"},

# This suite written to test push-down of aggregates and limits in combination
# with indexes, projections and order-by.
    "pushdown": {"schema": "pushdown-schema.py",
                 "ddl": "DDL.sql",
                 "template": "pushdown.sql",
                 "normalizer": "normalizer.py"},
# HSQL SEEMS TO HAVE A BAD DEFAULT PRECISION
# AND VoltDB gives VOLTDB ERROR: Type DECIMAL can't be cast as FLOAT, so keep it disabled, for now.
# If the only problem were HSQL HAS BAD DEFAULT PRECISION, we could USE FUZZY MATCHING.
# Enable this test to investigate the "DECIMAL can't be cast as FLOAT" issue
#    "advanced-decimal-fuzzy": {"schema": "decimal-schema.py",
#                               "ddl": "DDL.sql",
#                               "template": "advanced-decimal.sql",
#                               "normalizer": "fuzzynormalizer.py"},
}

<|endoftext|>


<commit_before># -* encoding: utf-8 *-
import os
import shutil
import tarfile
import argparse

from typing import Any

from gopythongo.packers import BasePacker
from gopythongo.utils import print_info, highlight, ErrorMessage

class TarGzPacker(BasePacker):
    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)

    @property
    def packer_name(self) -> str:
        return u"targz"

    def add_args(self, parser: argparse.ArgumentParser) -> None:
        pass

    def validate_args(self, args: argparse.Namespace) -> None:
        pass

    def _create_targzip(self, outfile: str, basepath: str, args: argparse.Namespace,
                        make_paths_relative: bool=False) -> None:
        """
        creates a .tar.gz of everything below basepath, making sure all
        stored paths are relative
        """
        if os.path.exists(outfile):
            os.remove(outfile)

        f = open(outfile, 'w')
        # we're using stream mode here as otherwise tarfile seems
        # to add spurious information about f's path to the gzip
        # wrapper... this can be seen inside 7-zip :(
        tf = tarfile.open(fileobj=f, mode='w|gz')
        for root, dir, files in os.walk(basepath):
            for filename in files:
                filepath = os.path.join(root, filename)
                arcpath = root
                if make_paths_relative:
                    arcpath = root[len(basepath):]
                arcname = os.path.join(arcpath, filename)
                if args.verbose:
                    print('adding %s as %s' % (filepath, arcname,))
                tf.add(filepath, arcname, recursive=False)
        tf.close()
        f.close()

    def _build_tar(self, args: argparse.Namespace) -> None:
        if args.collect_static:
            self._collect_static()

            if os.path.exists(args.static_root):
                print_info("creating static tarball of %s in %s" % (highlight(args.static_root),
                                                                    highlight(args.static_outfile),))
                self._create_targzip(args.static_outfile, args.static_root, args.static_relative)
            else:
                raise ErrorMessage("%s should now exist, but it doesn't" % highlight(args.static_root))

            if args.remove_static:
                print_info("removing static artifacts in %s" % highlight(args.static_root))
                shutil.rmtree(args.static_root)

        print_info("Creating bundle tarball of %s in %s" % (highlight(args.build_path), highlight(args.outfile),))
        self._create_targzip(args.outfile, args.build_path, args.bundle_relative)

    def pack(self, args: argparse.Namespace) -> None:
        pass

packer_class = TarGzPacker
<commit_msg>try to completely fix up the .tar.gz Packer<commit_after># -* encoding: utf-8 *-
import os
import shutil
import tarfile
import argparse

from typing import Any

from gopythongo.packers import BasePacker
from gopythongo.utils import print_info, highlight, ErrorMessage
from gopythongo.utils.buildcontext import the_context

class TarGzPacker(BasePacker):
    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)

    @property
    def packer_name(self) -> str:
        return u"targz"

    def add_args(self, parser: argparse.ArgumentParser) -> None:
        gp_tgz = parser.add_argument_group("Tar/Gzip Packer options")
        gp_tgz.add_argument("--targz-basename", dest="targz_basename", required=True,
                            help="Each .tar.gz created by each instance of --targz in the parameters will be named "
                                 "[basename]_[ext]-[version].tar.gz where [ext] is specified in --targz and [version] "
                                 "is retrieved from the selected Versioner")
        gp_tgz.add_argument("--targz", dest="targz", action="append", default=[],
                            help="Takes an argument in the form 'ext:path' where 'ext' is appended to the "
                                 "targz-basename and then the contents of path are stored and compressed in a .tar.gz "
                                 "archive. 'ext' is optional, but be careful to not overwrite your archives when you "
                                 "use multiple --targz arguments.")
        gp_tgz.add_argument("--targz-relative", dest="targz_relative", action="store_true", default=False,
                            help="Store relative paths in the .tar.gz archives.")

    def validate_args(self, args: argparse.Namespace) -> None:
        validate_fns = []  # type: List[str]
        for spec in args.targz:
            if ":" in spec:
                ext = spec.split(":", 1)[0]
                fn = "%s_%s" % (args.targz_basename, ext)
            else:
                fn = args.targz_basename

            if fn in validate_fns:
                raise ErrorMessage("Multiple --targz parameters result in the same filename '%s'." % highlight(fn))
            else:
                validate_fns.append(fn)

    def _create_targzip(self, outfile: str, basepath: str, args: argparse.Namespace,
                        make_paths_relative: bool=False) -> None:
        """
        creates a .tar.gz of everything below basepath, making sure all
        stored paths are relative
        """
        if os.path.exists(outfile):
            os.remove(outfile)

        f = open(outfile, 'w')
        # we're using stream mode here as otherwise tarfile seems
        # to add spurious information about f's path to the gzip
        # wrapper... this can be seen inside 7-zip :(
        tf = tarfile.open(fileobj=f, mode='w|gz')
        for root, dir, files in os.walk(basepath):
            for filename in files:
                filepath = os.path.join(root, filename)
                arcpath = root
                if make_paths_relative:
                    arcpath = root[len(basepath):]
                arcname = os.path.join(arcpath, filename)
                if args.verbose:
                    print('adding %s as %s' % (filepath, arcname,))
                tf.add(filepath, arcname, recursive=False)
        tf.close()
        f.close()

    def pack(self, args: argparse.Namespace) -> None:
        for spec in args.targz:
            if ":" in spec:
                ext, path = spec.split(":", 1)
                fn = "%s_%s-%s.tar.gz" % (args.targz_basename, ext,
                                          str(the_context.out_version.version))
            else:
                path = spec
                fn = "%s-%s.tar.gz" % (args.targz_basename, str(the_context.out_version.version))

            print_info("Creating bundle tarball of %s in %s" % (highlight(path), highlight(fn)))
            self._create_targzip(fn, path, args.targz_relative)

packer_class = TarGzPacker
<|endoftext|>