diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 00000000..e9ede421 --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,88 @@ +name: Funq CI +run-name: ${{ github.actor }} is testing out Funq + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.12"] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + host: linux + install-deps: true + setup-python: true + + - name: Install qmake + run: sudo apt-get install qt5-qmake + + - name: Display Python version + run: python -c "import sys; print(sys.version)" + + - name: Cache pip + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Setup client + run: python client/setup.py develop + + - name: Setup server + run: python server/setup.py develop + + test: + runs-on: ubuntu-latest + needs: build + strategy: + matrix: + python-version: ["3.12"] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Restore pip cache + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run client tests + run: python -m pytest funq/client/tests/ + + - name: Run server tests + run: make -C server/tests/ check + + - name: Run functional tests + run: pytest tests-functionnal/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5adf5a2d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,149 +0,0 @@ -sudo: required -cache: - - pip - - ccache -notifications: - email: - on_success: change - on_failure: change - -services: - - xvfb - -env: - global: - - FUNQ_CXXFLAGS="-Wall -Wextra -Werror" # make CI fail on warnings - -matrix: - fast_finish: true - include: - - # MacOS Qt5 Python2 - - os: osx - language: cpp - env: QT_SELECT=5 PYTHON=python2 - - # MacOS Qt5 Python3 - - os: osx - language: cpp - env: QT_SELECT=5 PYTHON=python3 - - # Linux Qt4 Python2.7 - - os: linux - dist: trusty - language: python - python: 2.7 - env: QT_SELECT=4 PYTHON=python - - # Linux Qt4 Python3.4 - - os: linux - dist: trusty - language: python - python: 3.4 - env: QT_SELECT=4 PYTHON=python - - # Linux Qt5.2.1 Python2.7 - - os: linux - dist: trusty - language: python - python: 2.7 - env: QT_SELECT=5.2.1 PYTHON=python - - # Linux Qt5.3.2 Python3.4 - - os: linux - dist: trusty - language: python - python: 3.4 - env: QT_SELECT=5.3.2 PYTHON=python - - # Linux Qt5.4.2 Python2.7 - - os: linux - dist: trusty - language: python - python: 2.7 - env: QT_SELECT=5.4.2 PYTHON=python - - # Linux Qt5.5.1 Python3.4 - - os: linux - dist: trusty - language: python - python: 3.4 - env: QT_SELECT=5.5.1 PYTHON=python - - # Linux Qt5.6.3 Python2.7 - - os: linux - dist: trusty - language: python - python: 2.7 - env: QT_SELECT=5.6.3 PYTHON=python - - # Linux Qt5.7.1 Python3.4 - - os: linux - dist: trusty - language: python - python: 3.4 - env: QT_SELECT=5.7.1 PYTHON=python - - # Linux Qt5.8 Python2.7 - - os: linux - dist: trusty - language: python - python: 2.7 - env: QT_SELECT=5.8 PYTHON=python - - # Linux Qt5.9.6 Python3.4 - - os: linux - dist: trusty - language: python - python: 3.4 - env: QT_SELECT=5.9.6 PYTHON=python - - # Linux Qt5.10.1 Python2.7 - - os: linux - dist: trusty - language: python - python: 2.7 - env: QT_SELECT=5.10.1 PYTHON=python - - # Linux Qt5.11.3 Python2.7 - - os: linux - dist: xenial - language: python - python: 2.7 - env: QT_SELECT=5.11.3 PYTHON=python - - # Linux Qt5.12.8 Python3.5 - - os: linux - dist: xenial - language: python - python: 3.5 - env: QT_SELECT=5.12.8 PYTHON=python - - # Linux Qt5.13.2 Python2.7 - - os: linux - dist: bionic - language: python - python: 2.7 - env: QT_SELECT=5.13.2 PYTHON=python - - # Linux Qt5.14.2 Python3.8 - - os: linux - dist: bionic - language: python - python: 3.8 - env: QT_SELECT=5.14.2 PYTHON=python - -install: - - source ./ci/install_dependencies.sh # source required because of exports - - $PYTHON --version # check exact python version - - qmake --version # check exact Qt version - -script: - - flake8 client/funq server/funq_server - - cd client && $PYTHON setup.py develop && cd .. - - cd server && $PYTHON setup.py develop && cd .. - - cd server/tests && qmake QMAKE_CXXFLAGS="$FUNQ_CXXFLAGS" && make -j4 && cd ../.. - - cd tests-functionnal/funq-test-app && qmake QMAKE_CXXFLAGS="$FUNQ_CXXFLAGS" && make -j4 && cd ../.. - - cd client && $PYTHON setup.py test; cd .. - - make -C server/tests/ check - - cd tests-functionnal && nosetests && cd .. diff --git a/README.rst b/README.rst index 4d264bf3..54e41bda 100644 --- a/README.rst +++ b/README.rst @@ -80,7 +80,7 @@ You can instead get the sources and install it:: For contributors, you may want to use **pip install -e** instead of **pip install** commands. Note that **virtualenv** is highly recommended, -so you can easily manage multiple python2/python3/Qt4/Qt5 environments. +so you can easily manage multiple python3/Qt4/Qt5 environments. When installing funq-server from sources, you can create a server/setup.cfg file to specify the qmake path:: diff --git a/client/doc/conf.py b/client/doc/conf.py index 08e3493c..939fc2bb 100644 --- a/client/doc/conf.py +++ b/client/doc/conf.py @@ -46,8 +46,8 @@ master_doc = 'index' # General information about the project. -project = u'funq' -copyright = u'2014, Julien Pagès' +project = 'funq' +copyright = '2014, Julien Pagès' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -189,8 +189,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'funq.tex', u'funq Documentation', - u'jpages', 'manual'), + ('index', 'funq.tex', 'funq Documentation', + 'jpages', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -219,8 +219,8 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'funq', u'funq Documentation', - [u'jpages'], 1) + ('index', 'funq', 'funq Documentation', + ['jpages'], 1) ] # If true, show URL addresses after external links. @@ -233,8 +233,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'funq', u'funq Documentation', - u'jpages', 'funq', 'One line description of project.', + ('index', 'funq', 'funq Documentation', + 'jpages', 'funq', 'One line description of project.', 'Miscellaneous'), ] diff --git a/client/doc/tutorial_test_widgets.py b/client/doc/tutorial_test_widgets.py index c3b81775..b5316f3f 100644 --- a/client/doc/tutorial_test_widgets.py +++ b/client/doc/tutorial_test_widgets.py @@ -22,7 +22,7 @@ def test_libelle_btn_test(self): btn_test = self.funq.widget('btnTest') properties = btn_test.properties() - self.assertEquals(properties['text'], 'Test') + self.assertEqual(properties['text'], 'Test') def test_open_dialog(self): """ @@ -31,7 +31,7 @@ def test_open_dialog(self): self.funq.widget('btnTest').click() dlg_label = self.funq.widget('dialog1_label') - self.assertEquals(dlg_label.properties()['text'], "Button clicked") + self.assertEqual(dlg_label.properties()['text'], "Button clicked") def test_tableview_content(self): """ @@ -39,12 +39,12 @@ def test_tableview_content(self): """ view = self.funq.widget('tableview') items = list(view.model_items().iter()) - self.assertEquals(len(items), 16) + self.assertEqual(len(items), 16) for item in items: text = "row {r}, column {c}".format(r=item.row, c=item.column) - self.assertEquals(item.value, text) + self.assertEqual(item.value, text) def test_some_treeview_content(self): """ @@ -52,8 +52,8 @@ def test_some_treeview_content(self): """ model = self.funq.widget('treeview').model_items() - item = model.item_by_named_path([u"item 1", u"item 1-2"]) - parent_item = model.item_by_named_path([u"item 1"]) + item = model.item_by_named_path(["item 1", "item 1-2"]) + parent_item = model.item_by_named_path(["item 1"]) - self.assertEquals(item.value, u"item 1-2") + self.assertEqual(item.value, "item 1-2") self.assertIn(item, parent_item.items) diff --git a/client/funq/aliases.py b/client/funq/aliases.py index 03bea0f7..54945984 100644 --- a/client/funq/aliases.py +++ b/client/funq/aliases.py @@ -37,7 +37,7 @@ complete widget's paths. """ -from ConfigParser import ConfigParser +from configparser import ConfigParser import collections from funq.errors import HooqAliasesInvalidLineError, HooqAliasesKeyError diff --git a/client/funq/client.py b/client/funq/client.py index 5ce8ccbb..4754e150 100644 --- a/client/funq/client.py +++ b/client/funq/client.py @@ -58,9 +58,9 @@ # python 3 compatibility # https://stackoverflow.com/questions/11301138/how-to-check-if-variable-is-string-with-python-2-and-3-compatibility) try: - basestring + str except NameError: - basestring = str + str = str class FunqClient(object): @@ -82,7 +82,7 @@ def __init__(self, host=None, port=None, aliases=None, if aliases is None: aliases = HooqAliases() - elif isinstance(aliases, basestring): + elif isinstance(aliases, str): aliases = HooqAliases.from_file(aliases) elif not isinstance(aliases, HooqAliases): raise TypeError("aliases must be None or str or an" @@ -156,8 +156,8 @@ def send_command(self, action, **kwargs): header = f.readline() if not header: raise FunqError("NoResponseFromApplication", - u"Pas de réponse de l'application testée -" - u" probablement un crash.") + "Pas de réponse de l'application testée -" + " probablement un crash.") to_read = int(header) response = json.loads(f.read(to_read).decode('utf-8')) if response.get('success') is False: @@ -323,7 +323,7 @@ def dump_widgets_list(self, stream='widgets_list.json', """ Write in a file the result of :meth:`widgets_list`. """ - if isinstance(stream, basestring): + if isinstance(stream, str): stream = open(stream, 'w') json.dump(self.widgets_list(with_properties=with_properties), stream, sort_keys=True, indent=4, separators=(',', ': ')) @@ -333,7 +333,7 @@ def take_screenshot(self, stream='screenshot.png', format_='PNG'): Take a screenshot of the active desktop. """ data = self.send_command('grab', format=format_) - if isinstance(stream, basestring): + if isinstance(stream, str): stream = open(stream, 'wb') raw = base64.standard_b64decode(data['data']) stream.write(raw) # pylint: disable=E1103 @@ -444,7 +444,7 @@ def _start_test_process(self, appconfig): env = os.environ # copy env - env = dict(env.items()) + env = dict(list(env.items())) env['FUNQ_ACTIVATION'] = '1' if funq_port: diff --git a/client/funq/errors.py b/client/funq/errors.py index 3c5b9bab..a7dc93de 100644 --- a/client/funq/errors.py +++ b/client/funq/errors.py @@ -49,7 +49,7 @@ class FunqError(Exception): def __init__(self, classname, desc): self.classname = classname self.desc = desc - Exception.__init__(self, u"{classname}: {desc}".format( + Exception.__init__(self, "{classname}: {desc}".format( classname=classname, desc=desc)) diff --git a/client/funq/models.py b/client/funq/models.py index a780a906..5e13813a 100644 --- a/client/funq/models.py +++ b/client/funq/models.py @@ -44,9 +44,9 @@ # python 3 compatibility # https://stackoverflow.com/questions/11301138/how-to-check-if-variable-is-string-with-python-2-and-3-compatibility) try: - basestring + str except NameError: - basestring = str + str = str class TreeItem(object): # pylint: disable=R0903 @@ -64,7 +64,7 @@ def create(cls, client, data): """ self = cls() self.client = client - for k, v in data.iteritems(): + for k, v in list(data.items()): if k != 'items': setattr(self, k, v) self.items = [cls.create(client, d) for d in data.get('items', [])] @@ -128,7 +128,7 @@ def __new__(mcs, name, bases, attrs): return cls -class Object(object): +class Object(object, metaclass=WidgetMetaClass): """ Allow to manipulate a QObject or derived. @@ -141,7 +141,6 @@ class Object(object): in inheritance order (ie 'QObject' is last) [type : list(str)] """ - __metaclass__ = WidgetMetaClass oid = None client = None @@ -161,7 +160,7 @@ def create(cls, client, data): break self = cls() - for k, v in data.iteritems(): + for k, v in list(data.items()): setattr(self, k, v) setattr(self, 'client', client) return self @@ -209,7 +208,7 @@ def wait_for_properties(self, props, timeout=10.0, timeout_interval=0.1): """ def check_props(): properties = self.properties() - for k, v in props.iteritems(): + for k, v in list(props.items()): if properties.get(k) != v: return False return True @@ -852,7 +851,7 @@ def dump_gitems(self, stream='gitems.json'): Write in a file the list of graphics items. """ data = self.client.send_command('graphicsitems', oid=self.oid) - if isinstance(stream, basestring): + if isinstance(stream, str): stream = open(stream, 'w') json.dump(data, stream, sort_keys=True, indent=4, separators=(',', ': ')) @@ -867,7 +866,7 @@ def grab_scene(self, stream, format_="PNG"): data = self.client.send_command('grab_graphics_view', format=format_, oid=self.oid) has_to_be_closed = False - if isinstance(stream, basestring): + if isinstance(stream, str): stream = open(stream, 'wb') has_to_be_closed = True raw = base64.standard_b64decode(data['data']) @@ -895,16 +894,12 @@ def set_current_text(self, text): """ Define the text of the combobox, ensuring that it is a possible value. """ - if not isinstance(text, basestring): + if not isinstance(text, str): raise TypeError('the text parameter must be a string' ' - got %s' % type(text)) column = self.properties()['modelColumn'] index = -1 - # WORKAROUND: Call items() via function pointer to prevent py2to3 from - # performing an illegal conversion which doesn't work on Python 3. This - # should be removed once we have "real" Python 3 compatibility. - items_func = AbstractItemModel.items - for item in items_func(self.model()).iter(): + for item in AbstractItemModel.items(): if column == int(item.column) and item.value == text: index = int(item.row) break diff --git a/client/funq/noseplugin.py b/client/funq/noseplugin.py deleted file mode 100644 index 22402a4c..00000000 --- a/client/funq/noseplugin.py +++ /dev/null @@ -1,202 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright: SCLE SFE -# Contributor: Julien Pagès -# -# This software is a computer program whose purpose is to test graphical -# applications written with the QT framework (http://qt.digia.com/). -# -# This software is governed by the CeCILL v2.1 license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". -# -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. -# -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. -# -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL v2.1 license and that you accept its terms. - -""" -Module that integrates funq with nosetests. -""" - -from funq.client import ApplicationRegistry -from funq.testcase import MultiFunqTestCase, FunqTestCase, \ - register_funq_app_registry -from funq.screenshoter import ScreenShoter -from funq import tools -from nose.plugins import Plugin -from ConfigParser import ConfigParser -import os -import codecs -import logging - -LOG = logging.getLogger('nose.plugins.funq') - - -def message_with_sep(message): - """Returns a message with a separator.""" - sep = '-' * 70 - return (sep, message, sep) - - -def locate_funq(): - """find the funq executable""" - return tools.which('funq') - - -class FunqPlugin(Plugin): - - """ - Nosetests plugin to integrate funq. - """ - name = 'funq' - - _instance = None - - @classmethod - def instance(cls): - return cls._instance - - def options(self, parser, env=None): - env = env or os.environ - super(FunqPlugin, self).options(parser, env=env) - parser.add_option('--funq-conf', - dest='funq_conf', - default=env.get('NOSE_FUNQ_CONF') or 'funq.conf', - help="funq configuration file, defaults to" - " `funq.conf` [NOSE_FUNQ_CONF].") - parser.add_option('--funq-gkit', - dest='funq_gkit', - default=env.get('NOSE_FUNQ_GKIT') or 'default', - help="Choose a specific graphic toolkit. This allows" - " to define default different aliases" - " Default: `default` [NOSE_FUNQ_GKIT]`") - gkit_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'aliases-gkits.conf') - parser.add_option('--funq-gkit-file', - dest='funq_gkit_file', - default=env.get('NOSE_FUNQ_GKIT_FILE') or gkit_file, - help="Override the file that defines graphic" - " toolkits. Default: `%s` [NOSE_FUNQ_GKIT_FILE]" - % gkit_file) - parser.add_option('--funq-attach-exe', - dest='funq_attach_exe', - default=env.get('NOSE_FUNQ_ATTACH_EXE') - or locate_funq(), - help="Complete path to the funq executable." - " [NOSE_FUNQ_ATTACH_EXE]") - parser.add_option('--funq-trace-tests', - dest='funq_trace_tests', - default=env.get('NOSE_FUNQ_TRACE_TESTS'), - help="A file in which start and end of tests will be" - " logged. [NOSE_FUNQ_TRACE_TESTS]") - parser.add_option('--funq-trace-tests-encoding', - dest='funq_trace_tests_encoding', - default=env.get('NOSE_FUNQ_TRACE_TESTS_ENCODING') - or 'utf-8', - help="encoding of the file used in" - " --funq-trace-tests." - " [NOSE_FUNQ_TRACE_TESTS_ENCODING]") - parser.add_option('--funq-screenshot-folder', - dest="funq_screenshot_folder", - default=env.get("NOSE_FUNQ_SCREENSHOT_FOLDER") - or os.path.realpath("screenshot-errors"), - help="Folder to saves screenshots on error." - " Default: screenshot-errors." - " [NOSE_FUNQ_SCREENSHOT_FOLDER]") - parser.add_option('--funq-snooze-factor', - dest="funq_snooze_factor", - default=env.get("NOSE_FUNQ_SNOOZE_FACTOR") - or 1.0, - help="Allow to specify a factor on every timeout." - " Default: 1.0. [NOSE_FUNQ_SNOOZE_FACTOR]") - - def configure(self, options, cfg): - Plugin.configure(self, options, cfg) - if not self.enabled: - return - conf_file = options.funq_conf = os.path.realpath(options.funq_conf) - if not os.path.isfile(conf_file): - raise Exception( - "Missing configuration file of funq: `%s`" % conf_file) - conf = ConfigParser() - conf.read([conf_file]) - self.app_registry = ApplicationRegistry() - self.app_registry.register_from_conf(conf, options) - register_funq_app_registry(self.app_registry) - self.trace_tests = options.funq_trace_tests - self.trace_tests_encoding = \ - options.funq_trace_tests_encoding - self.screenshoter = ScreenShoter(options.funq_screenshot_folder) - tools.SNOOZE_FACTOR = float(options.funq_snooze_factor) - FunqPlugin._instance = self - - def beforeTest(self, test): - message = u"Starting test `%s`" % test.id() - lines = message_with_sep(message) - for line in lines: - LOG.info(line) - if self.trace_tests: - with codecs.open(self.trace_tests, 'a', - self.trace_tests_encoding) as f: - f.write('\n'.join(lines)) - f.write('\n') - - def afterTest(self, test): - message = u"Ending test `%s`" % test.id() - lines = message_with_sep(message) - for line in lines: - LOG.info(line) - if self.trace_tests: - with codecs.open(self.trace_tests, 'a', - self.trace_tests_encoding) as f: - f.write('\n'.join(lines)) - f.write('\n') - - def describeTest(self, test): - return u'%s' % test.id() - - def take_screenshot(self, test): - if isinstance(test, MultiFunqTestCase): - if test.__app_config__: - for k, v in test.__app_config__.iteritems(): - if v.screenshot_on_error: - self.screenshoter.take_screenshot( - test.funq[k], - '%s [%s]' % (test.id(), k) - ) - elif isinstance(test, FunqTestCase): - if test.__app_config__: - if test.__app_config__.screenshot_on_error: - self.screenshoter.take_screenshot(test.funq, test.id()) - - def prepareTestResult(self, result): - _addError = result.addError - _addFailure = result.addFailure - - def addError(test, err): - self.take_screenshot(test.test) - _addError(test, err) - - def addFailure(test, err): - self.take_screenshot(test.test) - _addFailure(test, err) - result.addError = addError - result.addFailure = addFailure diff --git a/client/funq/pytest_plugin.py b/client/funq/pytest_plugin.py new file mode 100644 index 00000000..5ebb7937 --- /dev/null +++ b/client/funq/pytest_plugin.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +# Your copyright and license information here + +import pytest +from funq.client import ApplicationRegistry +from funq.testcase import register_funq_app_registry +from funq.screenshoter import ScreenShoter +from funq import tools +from configparser import ConfigParser +import os +import codecs +import logging + +LOG = logging.getLogger('pytest.plugins.funq') + + +def pytest_addoption(parser): + parser.addoption('--funq-conf', default='funq.conf', + help="funq configuration file, defaults to `funq.conf`.") + parser.addoption('--funq-gkit', default='default', + help="Choose a specific graphic toolkit.") + parser.addoption('--funq-gkit-file', default='aliases-gkits.conf', + help="Override the file that defines graphic toolkits.") + parser.addoption('--funq-attach-exe', default=None, + help="Complete path to the funq executable.") + parser.addoption('--funq-trace-tests', default=None, + help="A file in which start and end of tests will be logged.") + parser.addoption('--funq-trace-tests-encoding', default='utf-8', + help="encoding of the file used in --funq-trace-tests.") + parser.addoption('--funq-screenshot-folder', default="screenshot-errors", + help="Folder to saves screenshots on error.") + parser.addoption('--funq-snooze-factor', default=1.0, + help="Allow to specify a factor on every timeout.") + + +@pytest.hookimpl(tryfirst=True) +def pytest_configure(config): + conf_file = config.getoption('funq_conf') + if not os.path.isfile(conf_file): + raise Exception(f"Missing configuration file of funq: `{conf_file}`") + + conf = ConfigParser() + conf.read([conf_file]) + + app_registry = ApplicationRegistry() + app_registry.register_from_conf(conf, config.option) + register_funq_app_registry(app_registry) + + trace_tests = config.getoption('funq_trace_tests') + trace_tests_encoding = config.getoption('funq_trace_tests_encoding') + screenshoter = ScreenShoter(config.getoption('funq_screenshot_folder')) + tools.SNOOZE_FACTOR = float(config.getoption('funq_snooze_factor')) + + config.funq_plugin_data = { + 'app_registry': app_registry, + 'trace_tests': trace_tests, + 'trace_tests_encoding': trace_tests_encoding, + 'screenshoter': screenshoter, + } + + +def pytest_runtest_protocol(item, nextitem): + message = f"Starting test `{item.name}`" + lines = '-' * 70 + LOG.info(f"{lines}\n{message}\n{lines}") + + trace_tests = item.config.funq_plugin_data['trace_tests'] + trace_tests_encoding = item.config.funq_plugin_data['trace_tests_encoding'] + + if trace_tests: + with codecs.open(trace_tests, 'a', trace_tests_encoding) as f: + f.write(f"{lines}\n{message}\n{lines}\n") + + yield + + message = f"Ending test `{item.name}`" + LOG.info(f"{lines}\n{message}\n{lines}") + + if trace_tests: + with codecs.open(trace_tests, 'a', trace_tests_encoding) as f: + f.write(f"{lines}\n{message}\n{lines}\n") + + +@pytest.hookimpl(tryfirst=True) +def pytest_runtest_makereport(item, call): + if call.excinfo is not None: + screenshoter = item.config.funq_plugin_data['screenshoter'] + # Replace this with your logic to take a screenshot + screenshoter.take_screenshot(None, item.name) diff --git a/client/funq/screenshoter.py b/client/funq/screenshoter.py index 25e19c88..45a36193 100644 --- a/client/funq/screenshoter.py +++ b/client/funq/screenshoter.py @@ -67,7 +67,7 @@ def take_screenshot(self, funqclient, longname): if not os.path.isdir(self.working_folder): os.makedirs(self.working_folder) - bname = '{0}.png'.format(self.counter.next()) + bname = '{0}.png'.format(next(self.counter)) fname = os.path.join(self.working_folder, bname) try: @@ -75,9 +75,9 @@ def take_screenshot(self, funqclient, longname): except (SystemExit, KeyboardInterrupt): raise except Exception: - LOG.exception(u"impossible de prendre un screenshot pour" - u" %s", longname) + LOG.exception("impossible de prendre un screenshot pour" + " %s", longname) return with codecs.open(self.txt_file_path, "a", "utf-8") as f: - f.write(u"{0}: {1}\n".format(bname, longname)) + f.write("{0}: {1}\n".format(bname, longname)) diff --git a/client/funq/testcase.py b/client/funq/testcase.py index 6b686fba..01f01064 100644 --- a/client/funq/testcase.py +++ b/client/funq/testcase.py @@ -48,9 +48,9 @@ # python 3 compatibility # https://stackoverflow.com/questions/11301138/how-to-check-if-variable-is-string-with-python-2-and-3-compatibility) try: - unicode + str except NameError: - unicode = str + str = str class AssertionSuccessError(AssertionError): @@ -67,7 +67,7 @@ def __init__(self, name): self.name = name def __str__(self): - return u"Test %s passed but it is decorated as TODO" % self.name + return "Test %s passed but it is decorated as TODO" % self.name def __rep__(self): return self.__str__() @@ -100,8 +100,8 @@ def wrapper(*args, **kwargs): try: func(*args, **kwargs) except exception_cls as err: - err = u"%s" % err - if isinstance(err, unicode): + err = "%s" % err + if isinstance(err, str): err = err.encode( 'utf-8', errors='ignore') # pylint: disable=E1103 skip_msg = skip_message.encode('utf-8', errors='ignore') @@ -202,7 +202,7 @@ class MetaParameterized(type): RE_ESCAPE_BAD_CHARS = re.compile(r'[\.\(\) -/]') def __new__(cls, name, bases, attrs): - for k, v in attrs.items(): + for k, v in list(attrs.items()): if callable(v) and hasattr(v, 'parameters'): for func_suffix, args, kwargs in v.parameters: func_suffix = cls.RE_ESCAPE_BAD_CHARS.sub('_', func_suffix) @@ -246,7 +246,7 @@ def register_funq_app_registry(registry): BaseTestCase.__app_registry__ = registry -class BaseTestCase(unittest.TestCase): +class BaseTestCase(unittest.TestCase, metaclass=MetaParameterized): """ Abstract class of a testcase for Funq. @@ -257,7 +257,6 @@ class BaseTestCase(unittest.TestCase): It inherits from :class:`unittest.TestCase`, thus allowing to use very useful methods like assertEquals, assertFalse, etc. """ - __metaclass__ = MetaParameterized __app_registry__ = None longMessage = True @@ -279,7 +278,7 @@ def __delete_funq_ctx(self): def id(self): cls = self.__class__ fname = inspect.getsourcefile(cls)[len(os.getcwd()) + 1:] - return u"%s:%s.%s" % (fname, cls.__name__, self._testMethodName) + return "%s:%s.%s" % (fname, cls.__name__, self._testMethodName) class FunqTestCase(BaseTestCase): @@ -336,7 +335,7 @@ def __app_config__(cls): # pylint: disable=E0213 def _create_funq_ctx(self): ctx = {} self.funq = {} - for k, v in self.__app_config__.iteritems(): + for k, v in list(self.__app_config__.items()): ctx[k] = ApplicationContext(v) self.funq[k] = weakref.proxy(ctx[k].funq) return ctx diff --git a/client/funq/tests/__init__.py b/client/funq/tests/__init__.py index bc1862b8..e69de29b 100644 --- a/client/funq/tests/__init__.py +++ b/client/funq/tests/__init__.py @@ -1,9 +0,0 @@ -import os -from nose.loader import TestLoader -from nose.suite import LazySuite - - -def create_test_suite(): - this_dir = os.path.dirname(os.path.abspath(__file__)) - - return LazySuite(TestLoader().loadTestsFromDir(this_dir)) diff --git a/client/funq/tests/test_aliases.py b/client/funq/tests/test_aliases.py index 78b3a0a1..1d2adbb7 100644 --- a/client/funq/tests/test_aliases.py +++ b/client/funq/tests/test_aliases.py @@ -32,7 +32,7 @@ # The fact that you are presently reading this means that you have had # knowledge of the CeCILL v2.1 license and that you accept its terms. -from nose.tools import assert_equals, raises +import pytest from funq.aliases import HooqAliases, HooqAliasesInvalidLineError,\ HooqAliasesKeyError from tempfile import NamedTemporaryFile @@ -46,27 +46,28 @@ def setup(self): def test_add_one(self): self.aliases['1'] = '2' - assert_equals(self.aliases['1'], '2') + assert self.aliases['1'] == '2' - @raises(HooqAliasesKeyError) - def test_no_doublons(self): - self.aliases['1'] = '2' + def test_no_duplicates(self): self.aliases['1'] = '2' + with pytest.raises(HooqAliasesKeyError): + self.aliases['1'] = '2' + def test_substitution(self): self.aliases['a'] = '1' self.aliases['b'] = '{a}2' - assert_equals(self.aliases['b'], '12') + assert self.aliases['b'] == '12' - @raises(HooqAliasesKeyError) def test_bad_substitution(self): - self.aliases['b'] = '{a}2' - self.aliases['a'] = '1' - assert_equals(self.aliases['b'], '12') + with pytest.raises(HooqAliasesKeyError): + self.aliases['b'] = '{a}2' + self.aliases['a'] = '1' + assert self.aliases['b'] == '12' - @raises(HooqAliasesKeyError) def test_alias_inexistant(self): - self.aliases['b'] + with pytest.raises(HooqAliasesKeyError): + self.aliases['b'] class TestAliasesFromFile: @@ -97,7 +98,7 @@ def test_simple_parse(self): a = 1 b = 2 """) - assert_equals(aliases, {'a': '1', 'b': '2'}) + assert aliases == {'a': '1', 'b': '2'} def test_parse_with_comment(self): aliases = self._parse(""" @@ -105,7 +106,7 @@ def test_parse_with_comment(self): # toto = 1 b = 2 """) - assert_equals(aliases, {'a': '1', 'b': '2'}) + assert aliases == {'a': '1', 'b': '2'} def test_parse_with_empty_line(self): aliases = self._parse(""" @@ -113,11 +114,11 @@ def test_parse_with_empty_line(self): b = 2 """) - assert_equals(aliases, {'a': '1', 'b': '2'}) + assert aliases == {'a': '1', 'b': '2'} - @raises(HooqAliasesInvalidLineError) def test_parse_with_syntax_error(self): - self._parse(""" + with pytest.raises(HooqAliasesInvalidLineError): + self._parse(""" a 1 """) @@ -130,7 +131,7 @@ def test_with_gkit(self): a = {MY_DEFINE}::1 b = 2 """, gkit_data) - assert_equals(aliases, {'MY_DEFINE': '33', 'a': '33::1', 'b': '2'}) + assert aliases == {'MY_DEFINE': '33', 'a': '33::1', 'b': '2'} def test_with_gkit_interpolation(self): gkit_data = """ @@ -142,8 +143,8 @@ def test_with_gkit_interpolation(self): a = {MY_DEFINE}::1 b = {OTHER_DEFINE} """, gkit_data) - assert_equals(aliases, {'MY_DEFINE': '33', 'a': '33::1', - 'OTHER_DEFINE': '33::66', 'b': '33::66'}) + assert aliases == {'MY_DEFINE': '33', 'a': '33::1', + 'OTHER_DEFINE': '33::66', 'b': '33::66'} def test_with_gkit_custom(self): gkit_data = """ @@ -157,7 +158,7 @@ def test_with_gkit_custom(self): b = 2 """ aliases = self._parse(aliases_data, gkit_data) - assert_equals(aliases, {'MY_DEFINE': '33', 'a': '33::1', 'b': '2'}) + assert aliases == {'MY_DEFINE': '33', 'a': '33::1', 'b': '2'} aliases = self._parse(aliases_data, gkit_data, 'kde') - assert_equals(aliases, {'MY_DEFINE': '66', 'a': '66::1', 'b': '2'}) + assert aliases == {'MY_DEFINE': '66', 'a': '66::1', 'b': '2'} diff --git a/client/funq/tests/test_client.py b/client/funq/tests/test_client.py index 106c78b7..5bdc25f0 100644 --- a/client/funq/tests/test_client.py +++ b/client/funq/tests/test_client.py @@ -32,12 +32,12 @@ # The fact that you are presently reading this means that you have had # knowledge of the CeCILL v2.1 license and that you accept its terms. -from nose.tools import assert_equals, raises +import pytest from funq import client import os import subprocess -from ConfigParser import ConfigParser, NoOptionError +from configparser import ConfigParser, NoOptionError class ApplicationConfig(client.ApplicationConfig): @@ -47,7 +47,7 @@ class ApplicationConfig(client.ApplicationConfig): class GlobalOptions(object): def __init__(self, **kwds): - for k, v in kwds.iteritems(): + for k, v in list(kwds.items()): setattr(self, k, v) @@ -66,55 +66,55 @@ def createApplicationConfig(self): GlobalOptions(funq_conf=os.path.join(os.getcwd(), 'my.conf')) ) - @raises(NoOptionError) def test_require_executable(self): - self.createApplicationConfig() + with pytest.raises(NoOptionError): + self.createApplicationConfig() def test_abs_executable(self): self.set_opt('executable', os.getcwd()) appconf = self.createApplicationConfig() - assert_equals(appconf.executable, os.getcwd()) + assert appconf.executable == os.getcwd() def test_nonabs_executable(self): self.set_opt('executable', 'toto') appconf = self.createApplicationConfig() - assert_equals(appconf.executable, os.path.join(os.getcwd(), 'toto')) + assert appconf.executable == os.path.join(os.getcwd(), 'toto') def test_args(self): self.set_opt('executable', 'toto') self.set_opt('args', 'toto "titi 1" 2') appconf = self.createApplicationConfig() - assert_equals(appconf.args, ['toto', 'titi 1', '2']) + assert appconf.args == ['toto', 'titi 1', '2'] def test_port(self): self.set_opt('executable', 'toto') self.set_opt('funq_port', '12000') appconf = self.createApplicationConfig() - assert_equals(appconf.funq_port, 12000) + assert appconf.funq_port == 12000 def test_timeout_connection(self): self.set_opt('executable', 'toto') self.set_opt('timeout_connection', '5') appconf = self.createApplicationConfig() - assert_equals(appconf.timeout_connection, 5) + assert appconf.timeout_connection == 5 def test_abs_aliases(self): self.set_opt('executable', 'toto') self.set_opt('aliases', os.getcwd()) appconf = self.createApplicationConfig() - assert_equals(appconf.aliases, os.getcwd()) + assert appconf.aliases == os.getcwd() def test_nonabs_aliases(self): self.set_opt('executable', 'toto') self.set_opt('aliases', 'titi') appconf = self.createApplicationConfig() - assert_equals(appconf.aliases, os.path.join(os.getcwd(), 'titi')) + assert appconf.aliases == os.path.join(os.getcwd(), 'titi') def test_stdout_null(self): self.set_opt('executable', 'toto') self.set_opt('executable_stdout', 'NULL') appconf = self.createApplicationConfig() - assert_equals(appconf.executable_stdout, os.devnull) + assert appconf.executable_stdout == os.devnull class TestApplicationRegistry: @@ -130,9 +130,9 @@ def test_register_from_conf(self): self.reg.register_from_conf(conf, GlobalOptions(funq_conf='.')) - assert_equals(len(self.reg.confs), 1) + assert len(self.reg.confs) == 1 - assert_equals(self.reg.config('example').executable, exe) + assert self.reg.config('example').executable == exe class FakePopen(object): @@ -174,7 +174,7 @@ class OptionsDefault: ctx = client.ApplicationContext( appconf, client_class=lambda *a, **kwa: None) - assert_equals(ctx._process.command, ['funq', 'command']) + assert ctx._process.command == ['funq', 'command'] @FakePopen.patch_subprocess_popen def test_start_with_valgrind(self): @@ -189,4 +189,4 @@ class OptionsWithValgrind: ctx = client.ApplicationContext( appconf, client_class=lambda *a, **kwa: None) - assert_equals(ctx._process.command, ['funq', 'valgrind', 'command']) + assert ctx._process.command == ['funq', 'valgrind', 'command'] diff --git a/client/funq/tests/test_models.py b/client/funq/tests/test_models.py index df744d36..2829926f 100644 --- a/client/funq/tests/test_models.py +++ b/client/funq/tests/test_models.py @@ -32,7 +32,6 @@ # The fact that you are presently reading this means that you have had # knowledge of the CeCILL v2.1 license and that you accept its terms. -from nose.tools import assert_is_instance, assert_equals from funq import models @@ -43,7 +42,7 @@ class MyWidget(models.Widget): CPP_CLASS = 'TOTO' instance = models.Widget.create(None, {'classes': ['TOTO']}) - assert_is_instance(instance, MyWidget) + assert isinstance(instance, MyWidget) def test_multi_inheritance_priority(self): class MyWidget(models.Widget): @@ -53,7 +52,7 @@ class SomethingFirst(models.Widget): CPP_CLASS = 'TITI' instance = models.Widget.create(None, {'classes': ['TITI', 'TOTO']}) - assert_is_instance(instance, SomethingFirst) + assert isinstance(instance, SomethingFirst) def test_multi_inheritance(self): class MyWidget(models.Widget): @@ -61,7 +60,7 @@ class MyWidget(models.Widget): instance = models.Widget.create( None, {'classes': ['NotDefined', 'TOTO']}) - assert_is_instance(instance, MyWidget) + assert isinstance(instance, MyWidget) class TestModelItems: @@ -98,33 +97,33 @@ def setup(self): def test_row_by_named_path(self): items = self.model_items.row_by_named_path(['0-0', '0-0']) - assert_equals(items, self.model_items.items[0].items[0:2]) + assert items == self.model_items.items[0].items[0:2] def test_row_by_named_path_match_column(self): items = self.model_items.row_by_named_path( ['0-1', '0-1'], match_column=1) - assert_equals(items, self.model_items.items[1].items[0:2]) + assert items == self.model_items.items[1].items[0:2] def test_row_by_named_path_str_path(self): items = self.model_items.row_by_named_path('0-0/0-0') - assert_equals(items, self.model_items.items[0].items[0:2]) + assert items == self.model_items.items[0].items[0:2] def test_row_by_named_path_str_custom_path(self): items = self.model_items.row_by_named_path('0-0::0-0', sep='::') - assert_equals(items, self.model_items.items[0].items[0:2]) + assert items == self.model_items.items[0].items[0:2] def test_row_by_named_path_missing(self): items = self.model_items.row_by_named_path('blah/bluh') - assert_equals(items, None) + assert items is None def test_item_by_named_path(self): item = self.model_items.item_by_named_path(['0-0', '0-0']) - assert_equals(item, self.model_items.items[0].items[0]) + assert item == self.model_items.items[0].items[0] def test_item_by_named_path_column(self): item = self.model_items.item_by_named_path(['0-0', '0-0'], column=1) - assert_equals(item, self.model_items.items[0].items[1]) + assert item == self.model_items.items[0].items[1] def test_item_by_named_path_missing(self): item = self.model_items.item_by_named_path('blah/bluh') - assert_equals(item, None) + assert item is None diff --git a/client/funq/tests/test_noseplugin.py b/client/funq/tests/test_noseplugin.py deleted file mode 100644 index 31f89ac6..00000000 --- a/client/funq/tests/test_noseplugin.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright: SCLE SFE -# Contributor: Julien Pagès -# -# This software is a computer program whose purpose is to test graphical -# applications written with the QT framework (http://qt.digia.com/). -# -# This software is governed by the CeCILL v2.1 license under French law and -# abiding by the rules of distribution of free software. You can use, -# modify and/ or redistribute the software under the terms of the CeCILL -# license as circulated by CEA, CNRS and INRIA at the following URL -# "http://www.cecill.info". -# -# As a counterpart to the access to the source code and rights to copy, -# modify and redistribute granted by the license, users are provided only -# with a limited warranty and the software's author, the holder of the -# economic rights, and the successive licensors have only limited -# liability. -# -# In this respect, the user's attention is drawn to the risks associated -# with loading, using, modifying and/or developing or reproducing the -# software by the user in light of its specific status of free software, -# that may mean that it is complicated to manipulate, and that also -# therefore means that it is reserved for developers and experienced -# professionals having in-depth computer knowledge. Users are therefore -# encouraged to load and test the software's suitability as regards their -# requirements in conditions enabling the security of their systems and/or -# data to be ensured and, more generally, to use and operate it in the -# same conditions as regards security. -# -# The fact that you are presently reading this means that you have had -# knowledge of the CeCILL v2.1 license and that you accept its terms. - -from nose.tools import assert_equals, assert_true -from funq import noseplugin, tools -from optparse import OptionParser -import tempfile - - -def configured_plugin(argv=[], env={}): - plugin = noseplugin.FunqPlugin() - plugin.enabled = True - parser = OptionParser() - - plugin.options(parser) - options = parser.parse_args(argv)[0] - plugin.configure(options, None) - return plugin - - -class FakeTest(object): - - def __init__(self, test_id): - self.test_id = test_id - - def id(self): - return self.test_id - - -def test_simple_configure(): - tools.SNOOZE_FACTOR = 1.0 - - funqconf = tempfile.NamedTemporaryFile() - plugin = configured_plugin(["--funq-conf", funqconf.name]) - - assert_equals(1.0, tools.SNOOZE_FACTOR) - assert_true(plugin.screenshoter is not None) - assert_equals(plugin.trace_tests, None) - - -def test_snooze_factor_configure(): - tools.SNOOZE_FACTOR = 1.0 - - funqconf = tempfile.NamedTemporaryFile() - configured_plugin( - ["--funq-conf", funqconf.name, '--funq-snooze-factor', '3.2'] - ) - - assert_equals(3.2, tools.SNOOZE_FACTOR) - - -def test_before_after(): - noseplugin.FunqPlugin._current_test_name = None - - funqconf = tempfile.NamedTemporaryFile() - plugin = configured_plugin(["--funq-conf", funqconf.name]) - - test = FakeTest("id of test") - - plugin.beforeTest(test) - - plugin.afterTest(test) diff --git a/client/funq/tests/test_screenshoter.py b/client/funq/tests/test_screenshoter.py index f31f2d5b..08ad9436 100644 --- a/client/funq/tests/test_screenshoter.py +++ b/client/funq/tests/test_screenshoter.py @@ -32,7 +32,6 @@ # The fact that you are presently reading this means that you have had # knowledge of the CeCILL v2.1 license and that you accept its terms. -from nose.tools import assert_equals, assert_true from funq import screenshoter import tempfile import shutil @@ -64,9 +63,8 @@ def test_take_one_screenshot(): funq = FakeFunqClient() with ScreenShoterCtx() as ctx: ctx.take_screenshot(funq, "hello") - assert_equals(map(os.path.basename, funq.screens), ["0.png"]) - assert_true("0.png: hello" in open( - os.path.join(ctx.working_folder, 'images.txt')).read()) + assert list(map(os.path.basename, funq.screens)) == ["0.png"] + assert "0.png: hello" in open(os.path.join(ctx.working_folder, 'images.txt')).read() def test_take_screenshots(): @@ -76,7 +74,7 @@ def test_take_screenshots(): ctx.take_screenshot(funq, "thisisit") - assert_equals(map(os.path.basename, funq.screens), ["0.png", "1.png"]) + assert list(map(os.path.basename, funq.screens)) == ["0.png", "1.png"] content = open(os.path.join(ctx.working_folder, 'images.txt')).read() - assert_true("0.png: hello" in content) - assert_true("1.png: thisisit" in content) + assert "0.png: hello" in content + assert "1.png: thisisit" in content diff --git a/client/funq/tests/test_tools.py b/client/funq/tests/test_tools.py index 2836a7b8..69928019 100644 --- a/client/funq/tests/test_tools.py +++ b/client/funq/tests/test_tools.py @@ -32,7 +32,7 @@ # The fact that you are presently reading this means that you have had # knowledge of the CeCILL v2.1 license and that you accept its terms. -from nose.tools import assert_equals, assert_true, raises +import pytest from funq import tools import time import sys @@ -42,21 +42,21 @@ def test_wait_for(): def func(): return True - assert_true(tools.wait_for(func, 0.0)) + assert tools.wait_for(func, 0.0) -@raises(tools.TimeOutError) def test_wait_for_timeout(): def func(): return False - tools.wait_for(func, 0.0) + with pytest.raises(tools.TimeOutError): + tools.wait_for(func, 0.0) -@raises(Exception) def test_wait_for_custom_exc(): def func(): return Exception() - tools.wait_for(func, 0.0) + with pytest.raises(Exception): + tools.wait_for(func, 0.0) def test_wait_for_some_time(): @@ -64,11 +64,11 @@ def test_wait_for_some_time(): def func(): return t + 0.05 < time.time() - assert_true(tools.wait_for(func, 0.1)) + assert tools.wait_for(func, 0.1) def test_which(): - assert_equals(sys.executable, tools.which(sys.executable)) + assert sys.executable == tools.which(sys.executable) def test_which_with_pass(): @@ -77,14 +77,14 @@ def test_which_with_pass(): env = dict(PATH=path) os.environ = env try: - assert_equals(sys.executable, tools.which(fname)) + assert sys.executable == tools.which(fname) finally: os.environ = old_env def test_apply_snooze_factor(): tools.SNOOZE_FACTOR = 3.2 - assert_equals(6.4, tools.apply_snooze_factor(2)) + assert 6.4 == tools.apply_snooze_factor(2) tools.SNOOZE_FACTOR = 1.0 @@ -94,5 +94,5 @@ def test_wait_for_some_time_with_snooze_factor(): def func(): return t + 0.05 < time.time() - assert_true(tools.wait_for(func, 0.025)) + assert tools.wait_for(func, 0.025) tools.SNOOZE_FACTOR = 1.0 diff --git a/client/setup.py b/client/setup.py index a4557a98..963f02c5 100644 --- a/client/setup.py +++ b/client/setup.py @@ -4,8 +4,8 @@ import re import sys -if sys.version_info < (2, 7): - sys.exit("Python version must be > 2.7") +if sys.version_info < (3,): + sys.exit("Must be python3 version") def read(*paths): @@ -29,7 +29,6 @@ def read(*paths): version=version, packages=find_packages(), zip_safe=False, - use_2to3=True, test_suite='funq.tests.create_test_suite', install_requires=install_requires, package_data={'funq': ['aliases-gkits.conf']}, diff --git a/doc-dev/conf.py b/doc-dev/conf.py index 3ca13ed5..849cf649 100644 --- a/doc-dev/conf.py +++ b/doc-dev/conf.py @@ -40,8 +40,8 @@ master_doc = 'index' # General information about the project. -project = u'Documentation de développement sur le framework Funq (test_ihm_qt)' -copyright = u'2014, Julien Pagès' +project = 'Documentation de développement sur le framework Funq (test_ihm_qt)' +copyright = '2014, Julien Pagès' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -183,8 +183,8 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'DocumentationdedveloppementsurleframeworkFunqtest_ihm_qt.tex', u'Documentation de développement sur le framework Funq (test\\_ihm\\_qt) Documentation', - u'Julien Pagès', 'manual'), + ('index', 'DocumentationdedveloppementsurleframeworkFunqtest_ihm_qt.tex', 'Documentation de développement sur le framework Funq (test\\_ihm\\_qt) Documentation', + 'Julien Pagès', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -213,8 +213,8 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'documentationdedveloppementsurleframeworkfunqtest_ihm_qt', u'Documentation de développement sur le framework Funq (test_ihm_qt) Documentation', - [u'Julien Pagès'], 1) + ('index', 'documentationdedveloppementsurleframeworkfunqtest_ihm_qt', 'Documentation de développement sur le framework Funq (test_ihm_qt) Documentation', + ['Julien Pagès'], 1) ] # If true, show URL addresses after external links. @@ -227,8 +227,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'DocumentationdedveloppementsurleframeworkFunqtest_ihm_qt', u'Documentation de développement sur le framework Funq (test_ihm_qt) Documentation', - u'Julien Pagès', 'DocumentationdedveloppementsurleframeworkFunqtest_ihm_qt', 'One line description of project.', + ('index', 'DocumentationdedveloppementsurleframeworkFunqtest_ihm_qt', 'Documentation de développement sur le framework Funq (test_ihm_qt) Documentation', + 'Julien Pagès', 'DocumentationdedveloppementsurleframeworkFunqtest_ihm_qt', 'One line description of project.', 'Miscellaneous'), ] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e69de29b diff --git a/server/funq_server/runner.py b/server/funq_server/runner.py index 3c13bf31..a9b919e7 100644 --- a/server/funq_server/runner.py +++ b/server/funq_server/runner.py @@ -65,7 +65,7 @@ def __init__(self): self.system = platform.system() def _parse_args(self, argv=None): - desc = u"""Start a QT application with a libFunq server injected.""" + desc = """Start a QT application with a libFunq server injected.""" parser = argparse.ArgumentParser(description=desc) parser.add_argument('-v', '--version', action='version', version=funq_server.__version__) diff --git a/server/setup.py b/server/setup.py index cbfd4947..56e08b37 100644 --- a/server/setup.py +++ b/server/setup.py @@ -13,11 +13,8 @@ IS_WINDOWS = platform.system() == 'Windows' IS_MAC = platform.system() == 'Darwin' -if sys.version_info < (2, 7): - sys.exit("Python version must be > 2.7") -elif sys.version_info > (3,) and IS_WINDOWS: - sys.exit('funq server under windows require winappdbg' - ' which is not available under python 3 currenly.') +if sys.version_info < (3,): + sys.exit("Must be python3 version") install_requires = [] if IS_WINDOWS: diff --git a/tests-functionnal/test_action.py b/tests-functionnal/test_action.py index d40d4528..ac17dc0f 100644 --- a/tests-functionnal/test_action.py +++ b/tests-functionnal/test_action.py @@ -41,7 +41,7 @@ def test_blocking_trigger_to_nonblocking_action(self): self.start_dialog('action') action = self.funq.action(path='mainWindow::ActionDialog::nonblockingAction') action.trigger(blocking=True) - self.assertEquals(self.get_status_text(), 'nonblocking triggered !') + self.assertEqual(self.get_status_text(), 'nonblocking triggered !') def test_nonblocking_trigger_to_blocking_action(self): self.start_dialog('action') @@ -51,4 +51,4 @@ def test_nonblocking_trigger_to_blocking_action(self): btn = self.funq.widget(path='QMessageBox::qt_msgbox_buttonbox::QPushButton') btn.click() # now the status text must be updated - self.assertEquals(self.get_status_text(), 'blocking triggered !') + self.assertEqual(self.get_status_text(), 'blocking triggered !') diff --git a/tests-functionnal/test_click.py b/tests-functionnal/test_click.py index 06baf141..fc8f2547 100644 --- a/tests-functionnal/test_click.py +++ b/tests-functionnal/test_click.py @@ -42,25 +42,25 @@ def test_simple_click(self): self.start_dialog('click') btn = self.funq.widget(path='mainWindow::ClickDialog::QPushButton') btn.click() - self.assertEquals(self.get_status_text(), 'clicked !') + self.assertEqual(self.get_status_text(), 'clicked !') def test_right_click(self): self.start_dialog('widgetclick') btn = self.funq.widget(path='mainWindow::WidgetClickDialog') btn.click(btn='right') - self.assertEquals(self.get_status_text(), 'right clicked !') + self.assertEqual(self.get_status_text(), 'right clicked !') def test_middle_click(self): self.start_dialog('widgetclick') btn = self.funq.widget(path='mainWindow::WidgetClickDialog') btn.click(btn='middle') - self.assertEquals(self.get_status_text(), 'middle clicked !') + self.assertEqual(self.get_status_text(), 'middle clicked !') def test_double_click(self): self.start_dialog('doubleclick') btn = self.funq.widget(path='mainWindow::DoubleClickDialog') btn.dclick() - self.assertEquals(self.get_status_text(), 'double clicked !') + self.assertEqual(self.get_status_text(), 'double clicked !') @parameterized('sometext', 'Hello this is me !') @parameterized('someothertext', 'AAAA BBBBBBBBBBBBBBBBBB CCCCCCCCCCCCCCCCCCCC') @@ -68,7 +68,7 @@ def test_key_click(self, text): self.start_dialog('keyclick') line = self.funq.widget(path='mainWindow::KeyClickDialog::QLineEdit') line.keyclick(text) - self.assertEquals(self.get_status_text(), text) + self.assertEqual(self.get_status_text(), text) @parameterized('R1', 'H', 0, 0) @parameterized('R1_by_name', 'H', 'C1', 0) @@ -77,7 +77,7 @@ def test_click_header(self, orientation, index_or_name, result_index): self.start_dialog('table') header = self.funq.widget(path='mainWindow::TableDialog::QTableWidget::' + orientation) header.header_click(index_or_name) - self.assertEquals( + self.assertEqual( self.get_status_text(), orientation + " Header clicked: " + str(result_index) ) diff --git a/tests-functionnal/test_combobox.py b/tests-functionnal/test_combobox.py index 82b9ade5..514adb45 100644 --- a/tests-functionnal/test_combobox.py +++ b/tests-functionnal/test_combobox.py @@ -41,31 +41,31 @@ def test_set_current_text(self): self.start_dialog('combobox') cbx = self.funq.widget(path='mainWindow::ComboBoxDialog::QComboBox') cbx.set_current_text('Item 5') - self.assertEquals(self.get_status_text(), 'Text: Item 5') + self.assertEqual(self.get_status_text(), 'Text: Item 5') def test_model_items_count(self): self.start_dialog('combobox') cbx = self.funq.widget(path='mainWindow::ComboBoxDialog::QComboBox') - items = cbx.model().items() - self.assertEquals(len(items.items), 10) + items = list(cbx.model().items()) + self.assertEqual(len(items.items), 10) def test_model_items_value(self): self.start_dialog('combobox') cbx = self.funq.widget(path='mainWindow::ComboBoxDialog::QComboBox') - items = cbx.model().items() + items = list(cbx.model().items()) for i in range(10): - self.assertEquals(items.items[i].value, 'Item ' + str(i)) + self.assertEqual(items.items[i].value, 'Item ' + str(i)) def test_model_items_checkable(self): self.start_dialog('combobox') cbx = self.funq.widget(path='mainWindow::ComboBoxDialog::QComboBox') - items = cbx.model().items() + items = list(cbx.model().items()) for i in range(10): - self.assertEquals(items.items[i].is_checkable(), False) + self.assertEqual(items.items[i].is_checkable(), False) def test_model_items_checked(self): self.start_dialog('combobox') cbx = self.funq.widget(path='mainWindow::ComboBoxDialog::QComboBox') - items = cbx.model().items() + items = list(cbx.model().items()) for i in range(10): - self.assertEquals(items.items[i].is_checked(), False) + self.assertEqual(items.items[i].is_checked(), False) diff --git a/tests-functionnal/test_injection.py b/tests-functionnal/test_injection.py index f363cd20..3768300a 100644 --- a/tests-functionnal/test_injection.py +++ b/tests-functionnal/test_injection.py @@ -60,4 +60,4 @@ def test_close_blocking_msgbox_at_startup(self): btn.click() # now the main window should be visible window = self.funq.widget(path='mainWindow::QWidget') - self.assertEquals(window.properties()['visible'], True) + self.assertEqual(window.properties()['visible'], True) diff --git a/tests-functionnal/test_retrieve_widget.py b/tests-functionnal/test_retrieve_widget.py index d8d7ae57..cb272d57 100644 --- a/tests-functionnal/test_retrieve_widget.py +++ b/tests-functionnal/test_retrieve_widget.py @@ -50,8 +50,8 @@ def test_widget(self, alias=None, path=None): self.assertIsInstance(lbl, Widget) self.assertIsInstance(lbl.client, FunqClient) - self.assertEquals(lbl.path, 'mainWindow::RetrieveWidget::QLabel') - self.assertEquals(lbl.classes, [u'QLabel', u'QFrame', u'QWidget', u'QObject']) + self.assertEqual(lbl.path, 'mainWindow::RetrieveWidget::QLabel') + self.assertEqual(lbl.classes, ['QLabel', 'QFrame', 'QWidget', 'QObject']) self.assertTrue(lbl.oid) def test_widget_alias_unknow(self): @@ -59,7 +59,7 @@ def test_widget_alias_unknow(self): lbl = self.funq.widget('toto') def test_widget_path_unavailable(self): - with self.assertRaisesRegexp(FunqError, "InvalidWidgetPath"): + with self.assertRaisesRegex(FunqError, "InvalidWidgetPath"): lbl = self.funq.widget(path='toto', timeout=0.1) def test_widget_subclass(self): @@ -71,33 +71,33 @@ def test_widget_property(self): self.start_dialog('retrieve') lbl = self.funq.widget('lbl_retrieve') - self.assertEquals(lbl.properties()['text'], 'hello') + self.assertEqual(lbl.properties()['text'], 'hello') def test_widget_set_property(self): self.start_dialog('retrieve') lbl = self.funq.widget('lbl_retrieve') lbl.set_property('text', 'hello2') - self.assertEquals(lbl.properties()['text'], 'hello2') + self.assertEqual(lbl.properties()['text'], 'hello2') def test_widget_set_properties(self): self.start_dialog('retrieve') lbl = self.funq.widget('lbl_retrieve') lbl.set_properties(text='hello2', wordWrap=True) - self.assertEquals(lbl.properties()['text'], 'hello2') - self.assertEquals(lbl.properties()['wordWrap'], True) + self.assertEqual(lbl.properties()['text'], 'hello2') + self.assertEqual(lbl.properties()['wordWrap'], True) def test_widget_active(self): self.start_dialog('retrieve') self.funq.widget(path='mainWindow::RetrieveWidget') # wait for the dialog to be shown active = self.funq.active_widget('modal') - self.assertEquals(active.path, 'mainWindow::RetrieveWidget') + self.assertEqual(active.path, 'mainWindow::RetrieveWidget') @parameterized('V', 'V', ['R1', 'R2']) @parameterized('H', 'H', ['C1', 'C2', 'C3']) def test_headertexts(self, orientation, texts): self.start_dialog('table') header = self.funq.widget(path='mainWindow::TableDialog::QTableWidget::' + orientation) - self.assertEquals(header.header_texts(), texts) + self.assertEqual(header.header_texts(), texts) def test_header_from_table(self): self.start_dialog('table') diff --git a/tests-functionnal/test_shortcut.py b/tests-functionnal/test_shortcut.py index bbac1dc9..6d4dce98 100644 --- a/tests-functionnal/test_shortcut.py +++ b/tests-functionnal/test_shortcut.py @@ -47,7 +47,7 @@ def test_shortcut(self, sequence): dlg = self.funq.active_widget('modal') dlg.shortcut(sequence) - self.assertEquals(self.get_status_text(), "Shortcut: " + sequence) + self.assertEqual(self.get_status_text(), "Shortcut: " + sequence) @parameterized("CTRL+C,F2", ["CTRL+C", "F2"]) @parameterized("CTRL+C,F2,ENTER", ["CTRL+C", "F2", "ENTER"]) @@ -58,10 +58,10 @@ def test_multi_shortcut(self, sequences): for sequence in sequences: dlg.shortcut(sequence) - self.assertEquals(self.get_status_text(), "Shortcut: " + sequence) + self.assertEqual(self.get_status_text(), "Shortcut: " + sequence) def test_on_push_button(self): self.start_dialog('click') btn = self.funq.widget(path='mainWindow::ClickDialog::QPushButton') btn.shortcut("ENTER") - self.assertEquals(self.get_status_text(), 'clicked !') + self.assertEqual(self.get_status_text(), 'clicked !') diff --git a/tests-functionnal/test_tableview.py b/tests-functionnal/test_tableview.py index d140ef19..24959b87 100644 --- a/tests-functionnal/test_tableview.py +++ b/tests-functionnal/test_tableview.py @@ -40,35 +40,35 @@ class TestTableView(AppTestCase): def test_model_items_count(self): self.start_dialog('table') table = self.funq.widget(path='mainWindow::TableDialog::QTableWidget') - items = table.model().items() - self.assertEquals(len(items.items), 6) + items = list(table.model().items()) + self.assertEqual(len(items.items), 6) def test_model_items_value(self): self.start_dialog('table') table = self.funq.widget(path='mainWindow::TableDialog::QTableWidget') - items = table.model().items() + items = list(table.model().items()) for item in items.iter(): text = str(item.column) + '.' + str(item.row) - self.assertEquals(item.value, text) + self.assertEqual(item.value, text) def test_model_items_checkable(self): self.start_dialog('table') table = self.funq.widget(path='mainWindow::TableDialog::QTableWidget') - items = table.model().items() + items = list(table.model().items()) for item in items.iter(): - self.assertEquals(item.is_checkable(), False) + self.assertEqual(item.is_checkable(), False) def test_model_items_checked(self): self.start_dialog('table') table = self.funq.widget(path='mainWindow::TableDialog::QTableWidget') - items = table.model().items() + items = list(table.model().items()) for item in items.iter(): - self.assertEquals(item.is_checked(), False) + self.assertEqual(item.is_checked(), False) def test_model_item_select(self): self.start_dialog('table') table = self.funq.widget(path='mainWindow::TableDialog::QTableWidget') - items = table.model().items() + items = list(table.model().items()) for item in items.iter(): table.select_item(item) # Currently there is no way to automatically verify if the item is @@ -79,7 +79,7 @@ def test_model_item_select(self): def test_model_item_edit(self): self.start_dialog('table') table = self.funq.widget(path='mainWindow::TableDialog::QTableWidget') - items = table.model().items() + items = list(table.model().items()) for item in items.iter(): table.edit_item(item) # Currently there is no way to automatically verify if the item is @@ -90,7 +90,7 @@ def test_model_item_edit(self): def test_model_item_click(self): self.start_dialog('table') table = self.funq.widget(path='mainWindow::TableDialog::QTableWidget') - items = table.model().items() + items = list(table.model().items()) for item in items.iter(): table.click_item(item) # Currently there is no way to automatically verify if the item is @@ -101,7 +101,7 @@ def test_model_item_click(self): def test_model_item_dclick(self): self.start_dialog('table') table = self.funq.widget(path='mainWindow::TableDialog::QTableWidget') - items = table.model().items() + items = list(table.model().items()) for item in items.iter(): table.dclick_item(item) # Currently there is no way to automatically verify if the item is diff --git a/tests-functionnal/test_widget.py b/tests-functionnal/test_widget.py index cba9bc86..0505ca6e 100644 --- a/tests-functionnal/test_widget.py +++ b/tests-functionnal/test_widget.py @@ -43,95 +43,95 @@ def test_move_x(self): widget = self.funq.widget(path='mainWindow::WidgetClickDialog::QWidget') props_before = widget.properties() new_x, new_y = widget.move(x=5) - self.assertEquals(new_x, 5) - self.assertEquals(new_y, props_before['y']) + self.assertEqual(new_x, 5) + self.assertEqual(new_y, props_before['y']) props_after = widget.properties() - self.assertEquals(props_after['x'], new_x) - self.assertEquals(props_after['y'], new_y) + self.assertEqual(props_after['x'], new_x) + self.assertEqual(props_after['y'], new_y) def test_move_y(self): self.start_dialog('widgetclick') widget = self.funq.widget(path='mainWindow::WidgetClickDialog::QWidget') props_before = widget.properties() new_x, new_y = widget.move(y=6) - self.assertEquals(new_x, props_before['x']) - self.assertEquals(new_y, 6) + self.assertEqual(new_x, props_before['x']) + self.assertEqual(new_y, 6) props_after = widget.properties() - self.assertEquals(props_after['x'], new_x) - self.assertEquals(props_after['y'], new_y) + self.assertEqual(props_after['x'], new_x) + self.assertEqual(props_after['y'], new_y) def test_move_xy(self): self.start_dialog('widgetclick') widget = self.funq.widget(path='mainWindow::WidgetClickDialog::QWidget') new_x, new_y = widget.move(x=13, y=37) - self.assertEquals(new_x, 13) - self.assertEquals(new_y, 37) + self.assertEqual(new_x, 13) + self.assertEqual(new_y, 37) props_after = widget.properties() - self.assertEquals(props_after['x'], new_x) - self.assertEquals(props_after['y'], new_y) + self.assertEqual(props_after['x'], new_x) + self.assertEqual(props_after['y'], new_y) def test_resize_width(self): self.start_dialog('widgetclick') widget = self.funq.widget(path='mainWindow::WidgetClickDialog::QWidget') props_before = widget.properties() new_width, new_height = widget.resize(width=42) - self.assertEquals(new_width, 42) - self.assertEquals(new_height, props_before['height']) + self.assertEqual(new_width, 42) + self.assertEqual(new_height, props_before['height']) props_after = widget.properties() - self.assertEquals(props_after['width'], new_width) - self.assertEquals(props_after['height'], new_height) + self.assertEqual(props_after['width'], new_width) + self.assertEqual(props_after['height'], new_height) def test_resize_height(self): self.start_dialog('widgetclick') widget = self.funq.widget(path='mainWindow::WidgetClickDialog::QWidget') props_before = widget.properties() new_width, new_height = widget.resize(height=24) - self.assertEquals(new_width, props_before['width']) - self.assertEquals(new_height, 24) + self.assertEqual(new_width, props_before['width']) + self.assertEqual(new_height, 24) props_after = widget.properties() - self.assertEquals(props_after['width'], new_width) - self.assertEquals(props_after['height'], new_height) + self.assertEqual(props_after['width'], new_width) + self.assertEqual(props_after['height'], new_height) def test_resize_both(self): self.start_dialog('widgetclick') widget = self.funq.widget(path='mainWindow::WidgetClickDialog::QWidget') new_width, new_height = widget.resize(width=13, height=37) - self.assertEquals(new_width, 13) - self.assertEquals(new_height, 37) + self.assertEqual(new_width, 13) + self.assertEqual(new_height, 37) props_after = widget.properties() - self.assertEquals(props_after['width'], new_width) - self.assertEquals(props_after['height'], new_height) + self.assertEqual(props_after['width'], new_width) + self.assertEqual(props_after['height'], new_height) def test_map_position_to_from_global(self): btn = self.funq.widget(path='mainWindow::QWidget::click') local_pos = (10, 20) global_pos = btn.map_position_to(*local_pos, parent=None) - self.assertNotEquals(global_pos, local_pos) + self.assertNotEqual(global_pos, local_pos) local_pos_2 = btn.map_position_from(*global_pos, parent=None) - self.assertEquals(local_pos_2, local_pos) + self.assertEqual(local_pos_2, local_pos) def test_map_position_from_to_global(self): btn = self.funq.widget(path='mainWindow::QWidget::click') global_pos = (100, 200) local_pos = btn.map_position_from(*global_pos, parent=None) - self.assertNotEquals(local_pos, global_pos) + self.assertNotEqual(local_pos, global_pos) global_pos_2 = btn.map_position_to(*local_pos, parent=None) - self.assertEquals(global_pos_2, global_pos) + self.assertEqual(global_pos_2, global_pos) def test_map_position_to_from_parent(self): win = self.funq.widget(path='mainWindow') btn = self.funq.widget(path='mainWindow::QWidget::click') local_pos = (10, 20) parent_pos = btn.map_position_to(*local_pos, parent=win) - self.assertNotEquals(parent_pos, local_pos) + self.assertNotEqual(parent_pos, local_pos) local_pos_2 = btn.map_position_from(*parent_pos, parent=win) - self.assertEquals(local_pos_2, local_pos) + self.assertEqual(local_pos_2, local_pos) def test_map_position_from_to_parent(self): win = self.funq.widget(path='mainWindow') btn = self.funq.widget(path='mainWindow::QWidget::click') parent_pos = (100, 200) local_pos = btn.map_position_from(*parent_pos, parent=win) - self.assertNotEquals(local_pos, parent_pos) + self.assertNotEqual(local_pos, parent_pos) parent_pos_2 = btn.map_position_to(*local_pos, parent=win) - self.assertEquals(parent_pos_2, parent_pos) + self.assertEqual(parent_pos_2, parent_pos)