├── .github ├── FUNDING.yml ├── scripts │ └── generate-without-spyder.py ├── issue_template.md └── workflows │ └── run-tests.yml ├── requirements ├── conda.txt └── tests.txt ├── doc ├── screenshot.png └── example │ └── test_foo.py ├── .gitattributes ├── MANIFEST.in ├── spyder_unittest ├── locale │ ├── de │ │ └── LC_MESSAGES │ │ │ ├── spyder_unittest.mo │ │ │ └── spyder_unittest.po │ ├── es │ │ └── LC_MESSAGES │ │ │ ├── spyder_unittest.mo │ │ │ └── spyder_unittest.po │ ├── fr │ │ └── LC_MESSAGES │ │ │ ├── spyder_unittest.mo │ │ │ └── spyder_unittest.po │ ├── hr │ │ └── LC_MESSAGES │ │ │ ├── spyder_unittest.mo │ │ │ └── spyder_unittest.po │ ├── hu │ │ └── LC_MESSAGES │ │ │ ├── spyder_unittest.mo │ │ │ └── spyder_unittest.po │ ├── ja │ │ └── LC_MESSAGES │ │ │ ├── spyder_unittest.mo │ │ │ └── spyder_unittest.po │ ├── pl │ │ └── LC_MESSAGES │ │ │ ├── spyder_unittest.mo │ │ │ └── spyder_unittest.po │ ├── ru │ │ └── LC_MESSAGES │ │ │ ├── spyder_unittest.mo │ │ │ └── spyder_unittest.po │ ├── pt_BR │ │ └── LC_MESSAGES │ │ │ ├── spyder_unittest.mo │ │ │ └── spyder_unittest.po │ ├── zh_CN │ │ └── LC_MESSAGES │ │ │ ├── spyder_unittest.mo │ │ │ └── spyder_unittest.po │ └── spyder_unittest.pot ├── widgets │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ ├── test_configdialog.py │ │ ├── test_confpage.py │ │ └── test_datatree.py │ ├── confpage.py │ └── configdialog.py ├── backend │ ├── tests │ │ ├── __init__.py │ │ ├── test_zmqstream.py │ │ ├── test_frameworkregistry.py │ │ ├── test_nose2runner.py │ │ ├── test_abbreviator.py │ │ ├── test_runnerbase.py │ │ └── test_unittestrunner.py │ ├── __init__.py │ ├── workers │ │ ├── __init__.py │ │ ├── tests │ │ │ ├── test_print_versions.py │ │ │ └── test_unittestworker.py │ │ ├── zmqwriter.py │ │ ├── print_versions.py │ │ ├── unittestworker.py │ │ └── pytestworker.py │ ├── frameworkregistry.py │ ├── zmqreader.py │ ├── abbreviator.py │ ├── unittestrunner.py │ ├── nose2runner.py │ ├── runnerbase.py │ └── pytestrunner.py ├── __init__.py └── tests │ ├── conftest.py │ └── test_unittestplugin.py ├── .coveragerc ├── pytest.ini ├── AUTHORS ├── crowdin.yml ├── .gitignore ├── conftest.py ├── LICENSE.txt ├── pyproject.toml ├── RELEASE.md ├── .ciocheck └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: spyder 2 | -------------------------------------------------------------------------------- /requirements/conda.txt: -------------------------------------------------------------------------------- 1 | lxml 2 | spyder>=6,<7 3 | -------------------------------------------------------------------------------- /requirements/tests.txt: -------------------------------------------------------------------------------- 1 | flaky 2 | nose2 3 | pytest>=5 4 | pytest-qt 5 | -------------------------------------------------------------------------------- /doc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyder-ide/spyder-unittest/HEAD/doc/screenshot.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.md 2 | include LICENSE.txt 3 | include README.md 4 | recursive-include spyder_unittest *.py 5 | recursive-include spyder_unittest/locale *.mo 6 | -------------------------------------------------------------------------------- /spyder_unittest/locale/de/LC_MESSAGES/spyder_unittest.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyder-ide/spyder-unittest/HEAD/spyder_unittest/locale/de/LC_MESSAGES/spyder_unittest.mo -------------------------------------------------------------------------------- /spyder_unittest/locale/es/LC_MESSAGES/spyder_unittest.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyder-ide/spyder-unittest/HEAD/spyder_unittest/locale/es/LC_MESSAGES/spyder_unittest.mo -------------------------------------------------------------------------------- /spyder_unittest/locale/fr/LC_MESSAGES/spyder_unittest.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyder-ide/spyder-unittest/HEAD/spyder_unittest/locale/fr/LC_MESSAGES/spyder_unittest.mo -------------------------------------------------------------------------------- /spyder_unittest/locale/hr/LC_MESSAGES/spyder_unittest.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyder-ide/spyder-unittest/HEAD/spyder_unittest/locale/hr/LC_MESSAGES/spyder_unittest.mo -------------------------------------------------------------------------------- /spyder_unittest/locale/hu/LC_MESSAGES/spyder_unittest.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyder-ide/spyder-unittest/HEAD/spyder_unittest/locale/hu/LC_MESSAGES/spyder_unittest.mo -------------------------------------------------------------------------------- /spyder_unittest/locale/ja/LC_MESSAGES/spyder_unittest.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyder-ide/spyder-unittest/HEAD/spyder_unittest/locale/ja/LC_MESSAGES/spyder_unittest.mo -------------------------------------------------------------------------------- /spyder_unittest/locale/pl/LC_MESSAGES/spyder_unittest.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyder-ide/spyder-unittest/HEAD/spyder_unittest/locale/pl/LC_MESSAGES/spyder_unittest.mo -------------------------------------------------------------------------------- /spyder_unittest/locale/ru/LC_MESSAGES/spyder_unittest.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyder-ide/spyder-unittest/HEAD/spyder_unittest/locale/ru/LC_MESSAGES/spyder_unittest.mo -------------------------------------------------------------------------------- /spyder_unittest/locale/pt_BR/LC_MESSAGES/spyder_unittest.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyder-ide/spyder-unittest/HEAD/spyder_unittest/locale/pt_BR/LC_MESSAGES/spyder_unittest.mo -------------------------------------------------------------------------------- /spyder_unittest/locale/zh_CN/LC_MESSAGES/spyder_unittest.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyder-ide/spyder-unittest/HEAD/spyder_unittest/locale/zh_CN/LC_MESSAGES/spyder_unittest.mo -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | */tests/* 4 | 5 | [report] 6 | fail_under = 0 7 | show_missing = true 8 | skip_covered = true 9 | exclude_lines = 10 | pragma: no cover 11 | def test(): 12 | if __name__ == .__main__.: 13 | 14 | -------------------------------------------------------------------------------- /spyder_unittest/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Widgets for unittest plugin.""" 7 | -------------------------------------------------------------------------------- /spyder_unittest/backend/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2017 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Tests for spyder_unittest.backend .""" 7 | -------------------------------------------------------------------------------- /spyder_unittest/widgets/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2017 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Tests for spyder_unittest.widgets .""" 7 | -------------------------------------------------------------------------------- /spyder_unittest/backend/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Parts of the unittest plugin that are not related to the GUI.""" 7 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | # Copyright © Spyder Project Contributors 2 | # Licensed under the terms of the MIT License 3 | # (see LICENSE.txt for details) 4 | # 5 | # Configuration options for Pytest 6 | # 7 | 8 | [pytest] 9 | filterwarnings = 10 | ignore:.*There already exists a reference.*with id.*under the context:UserWarning 11 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The Spyder Project Contributors are composed of: 2 | 3 | * Pierre Raybaut (Original Spyder author). 4 | * Carlos Cordoba (Current maintainer). 5 | * All other developers who have contributed to this repository 6 | and/or the precursor in the main spyder-ide/spyder repository. 7 | 8 | -------------------------------------------------------------------------------- /spyder_unittest/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Spyder unittest plugin.""" 7 | 8 | # Local imports 9 | from .unittestplugin import UnitTestPlugin as PLUGIN_CLASS 10 | 11 | __version__ = '0.7.2.dev0' 12 | PLUGIN_CLASS 13 | -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | commit_message: '[ci skip] New %language% translation from Crowdin' 2 | append_commit_message: false 3 | files: 4 | - source: /spyder_unittest/locale/spyder_unittest.pot 5 | translation: /spyder_unittest/locale/%two_letters_code%/LC_MESSAGES/%file_name%.po 6 | languages_mapping: 7 | two_letters_code: 8 | pt-BR: pt_BR 9 | zh-CN: zh_CN 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | .cache 22 | 23 | # Installer logs 24 | pip-log.txt 25 | 26 | # Unit test / coverage reports 27 | .coverage 28 | .tox 29 | nosetests.xml 30 | 31 | # Mr Developer 32 | .mr.developer.cfg 33 | .project 34 | .pydevproject 35 | 36 | # OSX 37 | .DS_Store 38 | -------------------------------------------------------------------------------- /doc/example/test_foo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Example tests used to generate screenshots.""" 7 | 8 | import pytest 9 | 10 | def test_one_plus_one_is_two(): 11 | assert 1 + 1 == 2 12 | 13 | def test_two_plus_two_is_four(): 14 | assert 2 + 2 == 4 15 | 16 | def test_one_plus_two_is_five(): 17 | assert 1 + 2 == 5 18 | 19 | def test_two_times_two_is_four(): 20 | assert 2 * 2 == 4 21 | 22 | @pytest.mark.skip 23 | def test_will_be_skipped(): 24 | assert 0 == 1 25 | -------------------------------------------------------------------------------- /spyder_unittest/backend/workers/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | 7 | """ 8 | Code to be run in the target environment. 9 | 10 | This directory contains scripts and supporting modules that are supposed 11 | to be executed in the target environment (using the Python interpreter 12 | that the user specifies in the Preferences) instead of the environment that 13 | Spyder runs in. 14 | 15 | Dependencies should be kept to a minimum, because they need to be installed 16 | in each target environment. 17 | """ 18 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # 6 | 7 | """Configuration file for Pytest.""" 8 | 9 | # Standard library imports 10 | import os 11 | 12 | # To activate/deactivate certain things in Spyder when running tests. 13 | # NOTE: Please leave this before any other import here!! 14 | os.environ['SPYDER_PYTEST'] = 'True' 15 | 16 | # Third-party imports 17 | import pytest 18 | 19 | 20 | @pytest.fixture(autouse=True) 21 | def reset_conf_before_test(): 22 | from spyder.config.manager import CONF 23 | CONF.reset_to_defaults(notification=False) 24 | -------------------------------------------------------------------------------- /spyder_unittest/backend/tests/test_zmqstream.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2018 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Tests for zmqstream.py""" 7 | 8 | # Local imports 9 | from spyder_unittest.backend.zmqreader import ZmqStreamReader 10 | from spyder_unittest.backend.workers.zmqwriter import ZmqStreamWriter 11 | 12 | 13 | def test_zmqstream(qtbot): 14 | manager = ZmqStreamReader() 15 | worker = ZmqStreamWriter(manager.port) 16 | with qtbot.waitSignal(manager.sig_received) as blocker: 17 | worker.write(42) 18 | assert blocker.args == [[42]] 19 | worker.close() 20 | manager.close() 21 | -------------------------------------------------------------------------------- /.github/scripts/generate-without-spyder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | 7 | """Script to generate requirements/without-spyder.txt""" 8 | 9 | import re 10 | from pathlib import Path 11 | 12 | rootdir = Path(__file__).parents[2] 13 | input_filename = rootdir / 'requirements' / 'conda.txt' 14 | output_filename = rootdir / 'requirements' / 'without-spyder.txt' 15 | 16 | with open(input_filename) as infile: 17 | with open(output_filename, 'w') as outfile: 18 | for line in infile: 19 | package_name = re.match('[-a-z0-9_]*', line).group(0) 20 | if package_name != 'spyder': 21 | outfile.write(line) 22 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description of your problem 4 | 5 | 6 | 7 | ## What steps will reproduce the problem? 8 | 9 | 1. 10 | 2. 11 | 3. 12 | 13 | ## What is the expected output? What do you see instead? 14 | 15 | 16 | 17 | ### Paste traceback/error below (if applicable) 18 | *(Copy from error dialog or `View > Panes > Internal Console`)* 19 | 20 | ```python-traceback 21 | 22 | 23 | 24 | ``` 25 | 26 | ## Key versions and other information: 27 | 28 | * Spyder version: 29 | * Version of spyder-unittest plugin: 30 | * Installation method for Spyder and the unittest plugin: Anaconda / pip / ... 31 | * Python version: 32 | * Testing framework used: nose2 / pytest / unittest 33 | * Testing framework version: 34 | * Operating system: 35 | -------------------------------------------------------------------------------- /spyder_unittest/backend/tests/test_frameworkregistry.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2017 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Tests for frameworkregistry.py""" 7 | 8 | # Third party imports 9 | import pytest 10 | 11 | # Local imports 12 | from spyder_unittest.backend.frameworkregistry import FrameworkRegistry 13 | 14 | 15 | class MockRunner: 16 | name = 'foo' 17 | 18 | def __init__(self, *args): 19 | self.init_args = args 20 | 21 | 22 | def test_frameworkregistry_when_empty(): 23 | reg = FrameworkRegistry() 24 | with pytest.raises(KeyError): 25 | reg.create_runner('foo', None, 'temp.txt') 26 | 27 | 28 | def test_frameworkregistry_after_registering(): 29 | reg = FrameworkRegistry() 30 | reg.register(MockRunner) 31 | runner = reg.create_runner('foo', None, 'temp.txt') 32 | assert isinstance(runner, MockRunner) 33 | assert runner.init_args == (None, 'temp.txt') 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2013 Spyder Project Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /spyder_unittest/widgets/confpage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (c) 2023- Spyder Project Contributors 5 | # 6 | # Released under the terms of the MIT License 7 | # (see LICENSE.txt in the project root directory for details) 8 | # ----------------------------------------------------------------------------- 9 | 10 | """ 11 | Spyder-unittest Preferences Page. 12 | """ 13 | 14 | # Third party imports 15 | from qtpy.QtWidgets import QGroupBox, QVBoxLayout 16 | from spyder.api.preferences import PluginConfigPage 17 | from spyder.api.translations import get_translation 18 | 19 | # Localization 20 | _ = get_translation('spyder_unittest') 21 | 22 | 23 | class UnitTestConfigPage(PluginConfigPage): 24 | 25 | def setup_page(self) -> None: 26 | settings_group = QGroupBox(_('Settings')) 27 | widget = self.create_checkbox( 28 | _('Abbreviate test names'), 'abbrev_test_names', default=False) 29 | self.abbrev_box = widget.checkbox 30 | 31 | settings_layout = QVBoxLayout() 32 | settings_layout.addWidget(self.abbrev_box) 33 | settings_group.setLayout(settings_layout) 34 | 35 | vlayout = QVBoxLayout() 36 | vlayout.addWidget(settings_group) 37 | vlayout.addStretch(1) 38 | self.setLayout(vlayout) 39 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "spyder-unittest" 7 | description = "Plugin to run tests from within the Spyder IDE" 8 | readme = "README.md" 9 | requires-python = ">=3.10" 10 | license = "MIT" 11 | license-files = ["LICENSE.txt"] 12 | authors = [{name = "Spyder Project Contributors"}] 13 | keywords = ["Qt", "PyQt5", "PyQt6", "spyder", "plugins", "testing"] 14 | classifiers = [ 15 | "Development Status :: 4 - Beta", 16 | "Environment :: X11 Applications :: Qt", 17 | "Environment :: Win32 (MS Windows)", 18 | "Intended Audience :: Developers", 19 | "Operating System :: OS Independent", 20 | "Programming Language :: Python :: 3", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Programming Language :: Python :: 3.12", 24 | "Topic :: Software Development :: Testing", 25 | "Topic :: Text Editors :: Integrated Development Environments (IDE)" 26 | ] 27 | urls = {source = "https://github.com/spyder-ide/spyder-unittest"} 28 | dependencies = ["lxml", "spyder>=6,<7", "pyzmq"] 29 | dynamic = ["version"] 30 | 31 | [project.entry-points."spyder.plugins"] 32 | unittest = "spyder_unittest.unittestplugin:UnitTestPlugin" 33 | 34 | [tool.setuptools.dynamic] 35 | version = {attr = "spyder_unittest.__version__"} 36 | -------------------------------------------------------------------------------- /spyder_unittest/backend/tests/test_nose2runner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Tests for nose2runner.py""" 7 | 8 | # Local imports 9 | from spyder_unittest.backend.nose2runner import Nose2Runner 10 | from spyder_unittest.backend.runnerbase import Category 11 | 12 | 13 | def test_nose2runner_load_data(tmpdir): 14 | result_file = tmpdir.join('results') 15 | result_txt = """ 16 | 17 | 18 | 19 | 20 | 21 | text 22 | 23 | 24 | """ 25 | result_file.write(result_txt) 26 | runner = Nose2Runner(None, result_file.strpath) 27 | results = runner.load_data() 28 | assert len(results) == 2 29 | 30 | assert results[0].category == Category.OK 31 | assert results[0].status == 'ok' 32 | assert results[0].name == 'test_foo.test1' 33 | assert results[0].message == '' 34 | assert results[0].time == 0.04 35 | assert results[0].extra_text == [] 36 | 37 | assert results[1].category == Category.FAIL 38 | assert results[1].status == 'failure' 39 | assert results[1].name == 'test_foo.test2' 40 | assert results[1].message == 'test failure' 41 | assert results[1].time == 0.01 42 | assert results[1].extra_text == ['text'] 43 | -------------------------------------------------------------------------------- /spyder_unittest/tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """ 7 | Configuration file for Pytest. 8 | 9 | This contains the necessary definitions to make the main_window fixture 10 | available for integration tests. 11 | """ 12 | 13 | import time 14 | 15 | # Third-party imports 16 | from qtpy.QtWidgets import QApplication 17 | import pytest 18 | 19 | # QtWebEngineWidgets must be imported 20 | # before a QCoreApplication instance is created 21 | from qtpy import QtWebEngineWidgets # noqa 22 | 23 | # Spyder imports 24 | from spyder import version_info as spyder_version_info 25 | from spyder.api.plugin_registration.registry import PLUGIN_REGISTRY 26 | from spyder.app import start 27 | from spyder.config.base import running_in_ci 28 | from spyder.config.manager import CONF 29 | 30 | 31 | @pytest.fixture 32 | def main_window(monkeypatch): 33 | """Main Window fixture""" 34 | 35 | # Don't show tours message 36 | CONF.set('tours', 'show_tour_message', False) 37 | QApplication.processEvents() 38 | 39 | # Start the window 40 | window = start.main() 41 | QApplication.processEvents() 42 | 43 | yield window 44 | 45 | # This is to prevent "QThread: Thread still running" error 46 | if running_in_ci(): 47 | time.sleep(5) 48 | QApplication.processEvents() 49 | 50 | # Close main window 51 | window.closing(close_immediately=True) 52 | window.close() 53 | CONF.reset_to_defaults(notification=False) 54 | CONF.reset_manager() 55 | PLUGIN_REGISTRY.reset() 56 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | 1. Generate changelog: Run `loghub spyder-ide/spyder-unittest --milestone vx.y.z` 2 | 1. Edit changelog and commit 3 | 1. Bump version number in `spyder_unittest/__init__.py` 4 | 1. Remove non-versioned files: Run `git clean -xfdi` 5 | 1. Create source distribution: Run `python setup.py sdist` in root 6 | 1. Check that source distribution does not contain any unnecessary files (e.g., cache, `.pyc`) 7 | 1. Create wheel: Run `python setup.py bdist_wheel` 8 | 1. Test wheel: Uninstall current plugin and run `pip install dist/spyder_unittest-xxx.whl` 9 | 1. Check that `dist/` contains only the source distribution and wheel that you want to upload 10 | 1. Upload to PyPI: Run `twine upload dist/*` 11 | 1. Test: Uninstall current plugin and run `pip install spyder-unittest` 12 | 1. Commit `spyder_unittest/__init__.py` 13 | 1. Create a version tag on above commit: Run `git tag -a vx.y.z` 14 | 1. Change version number in `spyder_unittest/__init__.py` to `....dev0` and commit and push 15 | 1. If building conda package: 16 | 1. Wait for bot to submit PR **or** edit `meta.yaml` in fork of `spyder-unittest-feedstock`, changing version number and hash computed with `sha256sum dist/spyder_unittest-x.y.z.tar.gz`, test with `conda build conda.recipe`, and submit PR 17 | 1. When automatic tests on PR finish successfully, merge PR 18 | 1. Wait for CI to build Conda package 19 | 1. Copy: `anaconda copy conda-forge/spyder-unittest/x.y.z --to-owner spyder-ide` 20 | 1. Test Conda package: Uninstall current plugin and run `conda install -c spuder-ide spyder-unittest` 21 | 1. Push commits and version tag to `spyder-ide` repo: Run `git push remote_name vx.y.z` 22 | 1. Use GitHub to edit tag and publish release 23 | 1. Announce release on Google Groups 24 | -------------------------------------------------------------------------------- /spyder_unittest/backend/workers/tests/test_print_versions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Tests for print_versions.py""" 7 | 8 | from spyder_unittest.backend.workers.print_versions import ( 9 | get_nose2_info, get_pytest_info, get_unittest_info) 10 | 11 | 12 | def test_get_pytest_info_without_plugins(monkeypatch): 13 | import pytest 14 | monkeypatch.setattr(pytest, '__version__', '1.2.3') 15 | from _pytest.config import PytestPluginManager 16 | monkeypatch.setattr( 17 | PytestPluginManager, 18 | 'list_plugin_distinfo', lambda _: ()) 19 | expected = {'available': True, 'version': '1.2.3', 'plugins': {}} 20 | assert get_pytest_info() == expected 21 | 22 | 23 | def test_get_pytest_info_with_plugins(monkeypatch): 24 | import pytest 25 | import pkg_resources 26 | monkeypatch.setattr(pytest, '__version__', '1.2.3') 27 | dist1 = pkg_resources.Distribution(project_name='myPlugin1', 28 | version='4.5.6') 29 | dist2 = pkg_resources.Distribution(project_name='myPlugin2', 30 | version='7.8.9') 31 | from _pytest.config import PytestPluginManager 32 | monkeypatch.setattr( 33 | PytestPluginManager, 34 | 'list_plugin_distinfo', lambda _: (('1', dist1), ('2', dist2))) 35 | expected = {'available': True, 'version': '1.2.3', 36 | 'plugins': {'myPlugin1': '4.5.6', 'myPlugin2': '7.8.9'}} 37 | assert get_pytest_info() == expected 38 | 39 | 40 | def test_get_nose2_info(monkeypatch): 41 | import nose2 42 | monkeypatch.setattr(nose2, '__version__', '1.2.3') 43 | expected = {'available': True, 'version': '1.2.3', 'plugins': {}} 44 | assert get_nose2_info() == expected 45 | 46 | 47 | def test_get_unittest_imfo(monkeypatch): 48 | import platform 49 | monkeypatch.setattr(platform, 'python_version', lambda: '1.2.3') 50 | expected = {'available': True, 'version': '1.2.3', 'plugins': {}} 51 | assert get_unittest_info() == expected 52 | -------------------------------------------------------------------------------- /spyder_unittest/backend/workers/zmqwriter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """ 7 | Writer for sending stream of python objects over a ZMQ socket. 8 | 9 | The intended usage is that you construct a ZmqStreamReader in one process 10 | and a ZmqStreamWriter (with the same port number as the reader) in a worker 11 | process. The worker process can then use the stream to send its result to the 12 | reader. 13 | """ 14 | 15 | # Standard library imports 16 | import sys 17 | 18 | # Third party imports 19 | import zmq 20 | 21 | 22 | class ZmqStreamWriter: 23 | """Writer for sending stream of Python object over a ZMQ stream.""" 24 | 25 | def __init__(self, port: str) -> None: 26 | """ 27 | Constructor. 28 | 29 | Arguments 30 | --------- 31 | port : str 32 | TCP port number to be used for the stream. This should equal the 33 | `port` attribute of the corresponding `ZmqStreamReader`. 34 | """ 35 | context = zmq.Context() 36 | self.socket = context.socket(zmq.PAIR) 37 | self.socket.connect('tcp://localhost:{}'.format(port)) 38 | 39 | def write(self, obj: object) -> None: 40 | """Write arbitrary Python object to stream.""" 41 | self.socket.send_pyobj(obj) 42 | 43 | def close(self) -> None: 44 | """Close stream.""" 45 | self.socket.close() 46 | 47 | 48 | class FileStub(ZmqStreamWriter): 49 | """Stub for ZmqStreamWriter which instead writes to a file.""" 50 | 51 | def __init__(self, filename: str) -> None: 52 | """Constructor; connect to specified filename.""" 53 | self.file = open(filename, 'w') 54 | 55 | def write(self, obj: object) -> None: 56 | """Write Python object to file.""" 57 | self.file.write(str(obj) + '\n') 58 | 59 | def close(self) -> None: 60 | """Close file.""" 61 | self.file.close() 62 | 63 | 64 | if __name__ == '__main__': 65 | # Usage: python zmqwriter.py 66 | # Construct a ZMQ stream on the given port number and send the number 42 67 | # over the stream (for testing) 68 | worker = ZmqStreamWriter(sys.argv[1]) 69 | worker.write(42) 70 | -------------------------------------------------------------------------------- /spyder_unittest/backend/workers/print_versions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """ 7 | Script for checking which test frameworks are installed. 8 | 9 | This script prints a dictionary with the required info to stdout. 10 | """ 11 | 12 | 13 | def get_pytest_info(): 14 | """Return information about pytest.""" 15 | try: 16 | import pytest 17 | except ImportError: 18 | return {'available': False} 19 | 20 | plugins = {} 21 | 22 | class GetPluginVersionsPlugin(): 23 | def pytest_cmdline_main(self, config): 24 | nonlocal plugins 25 | plugininfo = config.pluginmanager.list_plugin_distinfo() 26 | plugins = {dist.project_name: dist.version 27 | for plugin, dist in plugininfo} 28 | return 0 # stop pytest, don't collect or run tests 29 | 30 | # --capture=sys needed on Windows to avoid 31 | # ValueError: saved filedescriptor not valid anymore 32 | pytest.main(['--capture=sys'], 33 | plugins=[GetPluginVersionsPlugin()]) 34 | 35 | return {'available': True, 36 | 'version': pytest.__version__, 37 | 'plugins': plugins} 38 | 39 | 40 | def get_nose2_info(): 41 | """ 42 | Return information about nose2. 43 | 44 | This only returns the version of nose2. The function does not gather any 45 | information about plugins. 46 | """ 47 | try: 48 | import nose2 49 | except ImportError: 50 | return {'available': False} 51 | 52 | return {'available': True, 53 | 'version': nose2.__version__, 54 | 'plugins': {}} 55 | 56 | 57 | def get_unittest_info(): 58 | """ 59 | Return versions of framework and its plugins. 60 | 61 | As 'unittest' is a built-in framework, we use the python version. 62 | """ 63 | from platform import python_version 64 | return {'available': True, 65 | 'version': python_version(), 66 | 'plugins': {}} 67 | 68 | 69 | def get_all_info(): 70 | """ 71 | Return information about all testing frameworks. 72 | 73 | Information is returned as a dictionary like the following: 74 | {'pytest': {'available': True, 'version': '7.1.1', 75 | 'plugins': {'flaky': '3.7.0', 'pytest-mock': '3.6.1'}}, 76 | 'nose2': {'available': False}, 77 | 'unittest': {'available': True, 'version': '3.10.5', 'plugins': {}}} 78 | """ 79 | return {'pytest': get_pytest_info(), 80 | 'nose2': get_nose2_info(), 81 | 'unittest': get_unittest_info()} 82 | 83 | 84 | if __name__ == '__main__': 85 | print(get_all_info()) 86 | -------------------------------------------------------------------------------- /spyder_unittest/backend/frameworkregistry.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2017 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Keep track of testing frameworks and create test runners when requested.""" 7 | 8 | from __future__ import annotations 9 | 10 | # Standard imports 11 | from typing import Optional, TYPE_CHECKING 12 | 13 | # Local imports 14 | if TYPE_CHECKING: 15 | from spyder_unittest.backend.runnerbase import RunnerBase 16 | from spyder_unittest.widgets.unittestgui import UnitTestWidget 17 | 18 | 19 | class FrameworkRegistry(): 20 | """ 21 | Registry of testing frameworks and their associated runners. 22 | 23 | The test runner for a framework is responsible for running the tests and 24 | parsing the results. It should implement the interface of RunnerBase. 25 | 26 | Frameworks should first be registered using `.register()`. This registry 27 | can then create the assoicated test runner when `.create_runner()` is 28 | called. 29 | 30 | Attributes 31 | ---------- 32 | frameworks : dict of (str, type) 33 | Dictionary mapping names of testing frameworks to the types of the 34 | associated runners. 35 | """ 36 | 37 | def __init__(self) -> None: 38 | """Initialize self.""" 39 | self.frameworks: dict[str, type[RunnerBase]] = {} 40 | 41 | def register(self, runner_class: type[RunnerBase]) -> None: 42 | """Register runner class for a testing framework. 43 | 44 | Parameters 45 | ---------- 46 | runner_class 47 | Class used for creating tests runners for the framework. 48 | """ 49 | self.frameworks[runner_class.name] = runner_class 50 | 51 | def create_runner(self, framework: str, widget: UnitTestWidget, 52 | tempfilename: Optional[str]) -> RunnerBase: 53 | """Create test runner associated to some testing framework. 54 | 55 | This creates an instance of the runner class whose `name` attribute 56 | equals `framework`. 57 | 58 | Parameters 59 | ---------- 60 | framework 61 | Name of testing framework. 62 | widget 63 | Unit test widget which constructs the test runner. 64 | resultfilename 65 | Name of file in which to store test results. If None, use default. 66 | 67 | Returns 68 | ------- 69 | RunnerBase 70 | Newly created test runner 71 | 72 | Exceptions 73 | ---------- 74 | KeyError 75 | Provided testing framework has not been registered. 76 | """ 77 | cls = self.frameworks[framework] 78 | return cls(widget, tempfilename) 79 | -------------------------------------------------------------------------------- /spyder_unittest/backend/tests/test_abbreviator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2017 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Tests for abbreviator.py""" 7 | 8 | # Local imports 9 | from spyder_unittest.backend.abbreviator import Abbreviator 10 | 11 | 12 | def test_abbreviator_with_one_word(): 13 | abb = Abbreviator() 14 | abb.add('ham') 15 | assert abb.abbreviate('ham') == 'ham' 16 | 17 | def test_abbreviator_with_one_word_with_two_components(): 18 | abb = Abbreviator() 19 | abb.add('ham.spam') 20 | assert abb.abbreviate('ham.spam') == 'h.spam' 21 | 22 | def test_abbreviator_with_one_word_with_three_components(): 23 | abb = Abbreviator() 24 | abb.add('ham.spam.eggs') 25 | assert abb.abbreviate('ham.spam.eggs') == 'h.s.eggs' 26 | 27 | def test_abbreviator_without_common_prefix(): 28 | abb = Abbreviator(['ham.foo', 'spam.foo']) 29 | assert abb.abbreviate('ham.foo') == 'h.foo' 30 | assert abb.abbreviate('spam.foo') == 's.foo' 31 | 32 | def test_abbreviator_with_prefix(): 33 | abb = Abbreviator(['test_ham.x', 'test_spam.x']) 34 | assert abb.abbreviate('test_ham.x') == 'test_h.x' 35 | assert abb.abbreviate('test_spam.x') == 'test_s.x' 36 | 37 | def test_abbreviator_with_first_word_prefix_of_second(): 38 | abb = Abbreviator(['ham.x', 'hameggs.x']) 39 | assert abb.abbreviate('ham.x') == 'ham.x' 40 | assert abb.abbreviate('hameggs.x') == 'hame.x' 41 | 42 | def test_abbreviator_with_second_word_prefix_of_first(): 43 | abb = Abbreviator(['hameggs.x', 'ham.x']) 44 | assert abb.abbreviate('hameggs.x') == 'hame.x' 45 | assert abb.abbreviate('ham.x') == 'ham.x' 46 | 47 | def test_abbreviator_with_three_words(): 48 | abb = Abbreviator(['hamegg.x', 'hameggs.x', 'hall.x']) 49 | assert abb.abbreviate('hamegg.x') == 'hamegg.x' 50 | assert abb.abbreviate('hameggs.x') == 'hameggs.x' 51 | assert abb.abbreviate('hall.x') == 'hal.x' 52 | 53 | def test_abbreviator_with_multilevel(): 54 | abb = Abbreviator(['ham.eggs.foo', 'ham.spam.bar', 'eggs.ham.foo', 55 | 'eggs.hamspam.bar']) 56 | assert abb.abbreviate('ham.eggs.foo') == 'h.e.foo' 57 | assert abb.abbreviate('ham.spam.bar') == 'h.s.bar' 58 | assert abb.abbreviate('eggs.ham.foo') == 'e.ham.foo' 59 | assert abb.abbreviate('eggs.hamspam.bar') == 'e.hams.bar' 60 | 61 | def test_abbreviator_with_one_word_and_parameters_with_dot(): 62 | abb = Abbreviator() 63 | abb.add('ham[.]') 64 | assert abb.abbreviate('ham[x.]') == 'ham[x.]' 65 | 66 | def test_abbreviator_with_one_word_with_two_components_and_parameters_with_dot(): 67 | abb = Abbreviator() 68 | abb.add('ham.spam[.]') 69 | assert abb.abbreviate('ham.spam[x.]') == 'h.spam[x.]' 70 | -------------------------------------------------------------------------------- /spyder_unittest/backend/zmqreader.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """ 7 | Reader for sending stream of python objects over a ZMQ socket. 8 | 9 | The intended usage is that you construct a ZmqStreamReader in one process 10 | and a ZmqStreamWriter (with the same port number as the reader) in a worker 11 | process. The worker process can then use the stream to send its result to the 12 | reader. 13 | """ 14 | 15 | # Third party imports 16 | from qtpy.QtCore import QObject, QProcess, QSocketNotifier, Signal 17 | from qtpy.QtWidgets import QApplication 18 | import zmq 19 | 20 | 21 | class ZmqStreamReader(QObject): 22 | """ 23 | Reader for receiving stream of Python objects via a ZMQ stream. 24 | 25 | Attributes 26 | ---------- 27 | port : int 28 | TCP port number used for the stream. 29 | 30 | Signals 31 | ------- 32 | sig_received(list) 33 | Emitted when objects are received; argument is list of received 34 | objects. 35 | """ 36 | 37 | sig_received = Signal(object) 38 | 39 | def __init__(self) -> None: 40 | """Constructor; also constructs ZMQ stream.""" 41 | super().__init__() 42 | self.context = zmq.Context() 43 | self.socket = self.context.socket(zmq.PAIR) 44 | self.port = self.socket.bind_to_random_port('tcp://*') 45 | fid = self.socket.getsockopt(zmq.FD) 46 | self.notifier = QSocketNotifier(fid, QSocketNotifier.Read, self) 47 | self.notifier.activated.connect(self.received_message) 48 | 49 | def received_message(self) -> None: 50 | """Called when a message is received.""" 51 | self.notifier.setEnabled(False) 52 | messages = [] 53 | try: 54 | while 1: 55 | message = self.socket.recv_pyobj(flags=zmq.NOBLOCK) 56 | messages.append(message) 57 | except zmq.ZMQError: 58 | pass 59 | finally: 60 | self.notifier.setEnabled(True) 61 | if messages: 62 | self.sig_received.emit(messages) 63 | 64 | def close(self) -> None: 65 | """Read any remaining messages and close stream.""" 66 | self.received_message() # Flush remaining messages 67 | self.notifier.setEnabled(False) 68 | self.socket.close() 69 | self.context.destroy() 70 | 71 | 72 | if __name__ == '__main__': 73 | # Usage: python zmqreader.py 74 | # Start zmqwriter.py in another process and construct a ZMQ stream between 75 | # this process and the zmqwriter process. Read and print what zmqwriter 76 | # sends over the ZMQ stream. 77 | 78 | import os.path 79 | import sys 80 | 81 | app = QApplication(sys.argv) 82 | manager = ZmqStreamReader() 83 | manager.sig_received.connect(print) 84 | process = QProcess() 85 | dirname = os.path.dirname(sys.argv[0]) 86 | writer_name = os.path.join(dirname, 'workers', 'zmqwriter.py') 87 | process.start('python', [writer_name, str(manager.port)]) 88 | process.finished.connect(app.quit) 89 | sys.exit(app.exec_()) 90 | -------------------------------------------------------------------------------- /spyder_unittest/backend/tests/test_runnerbase.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Tests for baserunner.py""" 7 | 8 | # Standard library imports 9 | import os 10 | from unittest.mock import Mock 11 | 12 | # Third party imports 13 | import pytest 14 | 15 | # Local imports 16 | from spyder_unittest.backend.runnerbase import RunnerBase 17 | from spyder_unittest.widgets.configdialog import Config 18 | 19 | 20 | def test_runnerbase_with_nonexisting_module(): 21 | class FooRunner(RunnerBase): 22 | module = 'nonexisiting' 23 | 24 | foo_runner = FooRunner(None) 25 | config = Config(foo_runner.module, 'wdir', True) 26 | 27 | with pytest.raises(NotImplementedError): 28 | foo_runner.create_argument_list(config, 'cov_path', None) 29 | 30 | with pytest.raises(NotImplementedError): 31 | foo_runner.finished(0) 32 | 33 | 34 | @pytest.mark.parametrize('pythonpath,env_pythonpath', [ 35 | ([], None), 36 | (['pythonpath'], None), 37 | (['pythonpath'], 'old') 38 | ]) 39 | def test_runnerbase_prepare_process(monkeypatch, pythonpath, env_pythonpath): 40 | MockQProcess = Mock() 41 | monkeypatch.setattr('spyder_unittest.backend.runnerbase.QProcess', 42 | MockQProcess) 43 | mock_process = MockQProcess() 44 | 45 | MockEnvironment = Mock() 46 | monkeypatch.setattr( 47 | 'spyder_unittest.backend.runnerbase.QProcessEnvironment.systemEnvironment', 48 | MockEnvironment) 49 | mock_environment = MockEnvironment() 50 | mock_environment.configure_mock(**{'value.return_value': env_pythonpath}) 51 | 52 | config = Config('myRunner', 'wdir') 53 | runner = RunnerBase(None, 'results') 54 | runner._prepare_process(config, pythonpath) 55 | 56 | mock_process.setWorkingDirectory.assert_called_once_with('wdir') 57 | mock_process.finished.connect.assert_called_once_with(runner.finished) 58 | if pythonpath: 59 | if env_pythonpath: 60 | mock_environment.insert.assert_any_call('PYTHONPATH', 61 | 'pythonpath{}{}'.format( 62 | os.pathsep, 63 | env_pythonpath)) 64 | else: 65 | mock_environment.insert.assert_any_call('PYTHONPATH', 'pythonpath') 66 | mock_process.setProcessEnvironment.assert_called_once() 67 | else: 68 | mock_environment.insert.assert_not_called() 69 | mock_process.setProcessEnvironment.assert_not_called() 70 | 71 | 72 | def test_runnerbase_start(monkeypatch): 73 | MockQProcess = Mock() 74 | monkeypatch.setattr('spyder_unittest.backend.runnerbase.QProcess', 75 | MockQProcess) 76 | mock_process = MockQProcess() 77 | 78 | mock_remove = Mock(side_effect=OSError()) 79 | monkeypatch.setattr('spyder_unittest.backend.runnerbase.os.remove', 80 | mock_remove) 81 | 82 | runner = RunnerBase(None, 'results') 83 | runner._prepare_process = lambda c, p: mock_process 84 | runner.create_argument_list = lambda c, cp, st: ['arg1', 'arg2'] 85 | config = Config('pytest', 'wdir', False) 86 | cov_path = None 87 | mock_process.waitForStarted = lambda: False 88 | with pytest.raises(RuntimeError): 89 | runner.start(config, cov_path, 'python_exec', ['pythondir'], None) 90 | 91 | mock_process.start.assert_called_once_with( 92 | 'python_exec', ['-X', 'utf8', 'arg1', 'arg2'] 93 | ) 94 | mock_remove.assert_called_once_with('results') 95 | -------------------------------------------------------------------------------- /spyder_unittest/backend/abbreviator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2017 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Class for abbreviating test names.""" 7 | 8 | from __future__ import annotations 9 | 10 | # Standard imports 11 | from dataclasses import dataclass 12 | 13 | @dataclass 14 | class Abbreviation: 15 | """ 16 | Abbreviation for one component of a test name. 17 | 18 | Abbreviations are defined recursively, so `.head` is the abbreviation 19 | for the first component and `.tail` specifies the abbreviations for the 20 | second and later components. 21 | """ 22 | head: str 23 | tail: Abbreviator 24 | 25 | 26 | class Abbreviator: 27 | """ 28 | Abbreviates names so that abbreviation identifies name uniquely. 29 | 30 | First, if the name contains brackets, the part in brackets starting at 31 | the first bracket is removed from the name. Then, all names are split 32 | in components separated by full stops (like module names in Python). 33 | Every component is abbreviated by the smallest prefix not shared by 34 | other names in the same directory, except for the last component which 35 | is not changed. Finally, the part in brackets, which was removed at the 36 | beginning, is appended to the abbreviated name. 37 | 38 | Attributes 39 | ---------- 40 | dic : dict of (str, [str, Abbreviator]) 41 | keys are the first-level components, values are a list, with the 42 | abbreviation as its first element and an Abbreviator for abbreviating 43 | the higher-level components as its second element. 44 | """ 45 | 46 | def __init__(self, names: list[str]=[]) -> None: 47 | """ 48 | Constructor. 49 | 50 | Arguments 51 | --------- 52 | names : list of str 53 | list of words which needs to be abbreviated. 54 | """ 55 | self.dic: dict[str, Abbreviation] = {} 56 | for name in names: 57 | self.add(name) 58 | 59 | def add(self, name: str) -> None: 60 | """ 61 | Add name to list of names to be abbreviated. 62 | 63 | Arguments 64 | --------- 65 | name : str 66 | """ 67 | name = name.split('[', 1)[0] 68 | if '.' not in name: 69 | return 70 | len_abbrev = 1 71 | start, rest = name.split('.', 1) 72 | for other in self.dic: 73 | if start[:len_abbrev] == other[:len_abbrev]: 74 | if start == other: 75 | break 76 | while (start[:len_abbrev] == other[:len_abbrev] 77 | and len_abbrev < len(start) 78 | and len_abbrev < len(other)): 79 | len_abbrev += 1 80 | if len_abbrev == len(start): 81 | self.dic[other].head = other[:len_abbrev + 1] 82 | elif len_abbrev == len(other): 83 | self.dic[other].head = other 84 | len_abbrev += 1 85 | else: 86 | if len(self.dic[other].head) < len_abbrev: 87 | self.dic[other].head = other[:len_abbrev] 88 | else: 89 | self.dic[start] = Abbreviation(start[:len_abbrev], Abbreviator()) 90 | self.dic[start].tail.add(rest) 91 | 92 | def abbreviate(self, name: str) -> str: 93 | """Return abbreviation of name.""" 94 | if '[' in name: 95 | name, parameters = name.split('[', 1) 96 | parameters = '[' + parameters 97 | else: 98 | parameters = '' 99 | if '.' in name: 100 | start, rest = name.split('.', 1) 101 | res = (self.dic[start].head 102 | + '.' + self.dic[start].tail.abbreviate(rest)) 103 | else: 104 | res = name 105 | return res + parameters 106 | -------------------------------------------------------------------------------- /spyder_unittest/backend/unittestrunner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2017 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Support for unittest framework.""" 7 | 8 | from __future__ import annotations 9 | 10 | # Standard library imports 11 | import os.path as osp 12 | from typing import Any, Optional 13 | 14 | # Local imports 15 | from spyder_unittest.widgets.configdialog import Config 16 | from spyder_unittest.backend.runnerbase import Category, RunnerBase, TestResult 17 | from spyder_unittest.backend.zmqreader import ZmqStreamReader 18 | 19 | 20 | class UnittestRunner(RunnerBase): 21 | """Class for running tests with unittest module in standard library.""" 22 | 23 | module = 'unittest' 24 | name = 'unittest' 25 | 26 | def create_argument_list(self, config: Config, 27 | cov_path: Optional[str], 28 | single_test: Optional[str]) -> list[str]: 29 | """Create argument list for testing process.""" 30 | dirname = osp.dirname(__file__) 31 | pyfile = osp.join(dirname, 'workers', 'unittestworker.py') 32 | arguments = [pyfile, str(self.reader.port)] 33 | if single_test: 34 | arguments.append(single_test) 35 | arguments += config.args 36 | return arguments 37 | 38 | def start(self, config: Config, cov_path: Optional[str], 39 | executable: str, pythonpath: list[str], 40 | single_test: Optional[str]) -> None: 41 | """Start process which will run the unit test suite.""" 42 | self.config = config 43 | self.reader = ZmqStreamReader() 44 | self.reader.sig_received.connect(self.process_output) 45 | super().start(config, cov_path, executable, pythonpath, single_test) 46 | 47 | def finished(self, exitcode: int) -> None: 48 | """ 49 | Called when the unit test process has finished. 50 | 51 | This function reads the process output and emits `sig_finished`. 52 | """ 53 | self.reader.close() 54 | output = self.read_all_process_output() 55 | self.sig_finished.emit([], output, True) 56 | 57 | def process_output(self, output: list[dict[str, Any]]) -> None: 58 | """ 59 | Process output of test process. 60 | 61 | Parameters 62 | ---------- 63 | output : list 64 | list of decoded Python object sent by test process. 65 | """ 66 | collected_list = [] 67 | starttest_list = [] 68 | result_list = [] 69 | 70 | for result_item in output: 71 | if result_item['event'] == 'collected': 72 | collected_list.append(result_item['id']) 73 | elif result_item['event'] == 'startTest': 74 | starttest_list.append(result_item['id']) 75 | elif result_item['event'].startswith('add'): 76 | testresult = add_event_to_testresult(result_item) 77 | result_list.append(testresult) 78 | 79 | if collected_list: 80 | self.sig_collected.emit(collected_list) 81 | if starttest_list: 82 | self.sig_starttest.emit(starttest_list) 83 | if result_list: 84 | self.sig_testresult.emit(result_list) 85 | 86 | 87 | def add_event_to_testresult(event: dict[str, Any]) -> TestResult: 88 | """Convert an addXXX event sent by test process to a TestResult.""" 89 | status = event['event'][3].lower() + event['event'][4:] 90 | if status in ('error', 'failure', 'unexpectedSuccess'): 91 | cat = Category.FAIL 92 | elif status in ('success', 'expectedFailure'): 93 | cat = Category.OK 94 | else: 95 | cat = Category.SKIP 96 | testname = event['id'] 97 | message = event.get('reason', '') 98 | extra_text = event.get('err', '') 99 | result = TestResult(cat, status, testname, message=message, 100 | extra_text=extra_text) 101 | return result 102 | -------------------------------------------------------------------------------- /.ciocheck: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # ciocheck 3 | # https://github.com/ContinuumIO/ciocheck 4 | # ----------------------------------------------------------------------------- 5 | [ciocheck] 6 | branch = origin/master 7 | diff_mode = commited 8 | file_mode = all 9 | check = pep8,pydocstyle,flake8,isort,yapf,coverage,pytest 10 | enforce = pep8,pydocstyle,flake8,isort,yapf,coverage,pytest 11 | 12 | # Python (pyformat) 13 | header = # -*- coding: utf-8 -*- 14 | copyright_file = .ciocopyright 15 | add_copyright = true 16 | add_header = true 17 | add_init = true 18 | 19 | # ----------------------------------------------------------------------------- 20 | # pep8 21 | # https://pep8.readthedocs.io/en/release-1.7.x/intro.html#configuration 22 | # ----------------------------------------------------------------------------- 23 | [pep8] 24 | exclude = */tests/* 25 | ignore = E126,W503 26 | max-line-length = 79 27 | 28 | # ----------------------------------------------------------------------------- 29 | # pydocstyle 30 | # http://www.pydocstyle.org/en/latest/usage.html#example 31 | # ----------------------------------------------------------------------------- 32 | [pydocstyle] 33 | add-ignore = D203, 34 | inherit = false 35 | match = .*/(?!test)[^/]*.py 36 | 37 | # ----------------------------------------------------------------------------- 38 | # Flake 8 39 | # http://flake8.readthedocs.io/en/latest/config.html 40 | # ----------------------------------------------------------------------------- 41 | [flake8] 42 | exclude = */tests/* 43 | ignore = E126,W503 44 | max-line-length = 79 45 | max-complexity = 64 46 | 47 | # ----------------------------------------------------------------------------- 48 | # pylint 49 | # https://pylint.readthedocs.io/en/latest/ 50 | # ----------------------------------------------------------------------------- 51 | #[pylint:messages] 52 | 53 | # ----------------------------------------------------------------------------- 54 | # isort 55 | # https://github.com/timothycrosley/isort/wiki/isort-Settings 56 | # ----------------------------------------------------------------------------- 57 | [isort] 58 | from_first = true 59 | import_heading_stdlib = Standard library imports 60 | import_heading_thirdparty = Third party imports 61 | import_heading_firstparty = Local imports 62 | import_heading_localfolder = Local imports 63 | indent = ' ' 64 | known_first_party = spyder_unittest 65 | known_third_party = spyder,pytest,pytestqt,nose 66 | line_length = 79 67 | sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER 68 | 69 | # ----------------------------------------------------------------------------- 70 | # YAPF 71 | # https://github.com/google/yapf#formatting-style 72 | # ----------------------------------------------------------------------------- 73 | [yapf:style] 74 | based_on_style = pep8 75 | column_limit = 79 76 | spaces_before_comment = 2 77 | split_before_named_assigned = False 78 | 79 | # ----------------------------------------------------------------------------- 80 | # autopep8 81 | # http://pep8.readthedocs.io/en/latest/intro.html#configuration 82 | # ----------------------------------------------------------------------------- 83 | [autopep8] 84 | exclude = */tests/* 85 | ignore = E126, 86 | max-line-length = 99 87 | aggressive = 0 88 | 89 | # ----------------------------------------------------------------------------- 90 | # Coverage 91 | # http://coverage.readthedocs.io/en/latest/config.html 92 | # ----------------------------------------------------------------------------- 93 | [coverage:run] 94 | omit = 95 | */tests/* 96 | 97 | [coverage:report] 98 | fail_under = 0 99 | show_missing = true 100 | skip_covered = true 101 | exclude_lines = 102 | pragma: no cover 103 | def test(): 104 | if __name__ == .__main__.: 105 | 106 | # ----------------------------------------------------------------------------- 107 | # pytest 108 | # http://doc.pytest.org/en/latest/usage.html 109 | # ----------------------------------------------------------------------------- 110 | [pytest] 111 | addopts = -rfew --durations=10 112 | python_functions = test_* 113 | python_files = test_*.py 114 | -------------------------------------------------------------------------------- /spyder_unittest/backend/nose2runner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Support for Nose framework.""" 7 | 8 | from __future__ import annotations 9 | 10 | # Standard library imports 11 | from typing import Optional, TYPE_CHECKING 12 | 13 | # Third party imports 14 | from lxml import etree 15 | from spyder.config.base import get_translation 16 | 17 | # Local imports 18 | from spyder_unittest.backend.runnerbase import Category, RunnerBase, TestResult 19 | if TYPE_CHECKING: 20 | from spyder_unittest.widgets.configdialog import Config 21 | 22 | try: 23 | _ = get_translation('spyder_unittest') 24 | except KeyError: 25 | import gettext 26 | _ = gettext.gettext 27 | 28 | 29 | class Nose2Runner(RunnerBase): 30 | """Class for running tests within Nose framework.""" 31 | 32 | module = 'nose2' 33 | name = 'nose2' 34 | 35 | def create_argument_list(self, config: Config, 36 | cov_path: Optional[str], 37 | single_test: Optional[str]) -> list[str]: 38 | """Create argument list for testing process.""" 39 | arguments = [ 40 | '-m', self.module, '--plugin=nose2.plugins.junitxml', 41 | '--junit-xml', '--junit-xml-path={}'.format(self.resultfilename) 42 | ] 43 | if single_test: 44 | arguments.append(single_test) 45 | arguments += config.args 46 | return arguments 47 | 48 | def finished(self, exitcode: int) -> None: 49 | """Called when the unit test process has finished.""" 50 | output = self.read_all_process_output() 51 | testresults = self.load_data() 52 | self.sig_finished.emit(testresults, output, True) 53 | 54 | def load_data(self) -> list[TestResult]: 55 | """ 56 | Read and parse unit test results. 57 | 58 | This function reads the unit test results from the file with name 59 | `self.resultfilename` and parses them. The file should contain the 60 | test results in JUnitXML format. 61 | 62 | Returns 63 | ------- 64 | list of TestResult 65 | Unit test results. 66 | """ 67 | try: 68 | data = etree.parse(self.resultfilename).getroot() 69 | except OSError: 70 | return [] 71 | 72 | testresults = [] 73 | for testcase in data: 74 | category = Category.OK 75 | status = 'ok' 76 | name = '{}.{}'.format(testcase.get('classname'), 77 | testcase.get('name')) 78 | message = '' 79 | time = float(testcase.get('time')) 80 | extras = [] 81 | 82 | for child in testcase: 83 | if child.tag in ('error', 'failure', 'skipped'): 84 | if child.tag == 'skipped': 85 | category = Category.SKIP 86 | else: 87 | category = Category.FAIL 88 | status = child.tag 89 | type_ = child.get('type') 90 | message = child.get('message', default='') 91 | if type_ and message: 92 | message = '{0}: {1}'.format(type_, message) 93 | elif type_: 94 | message = type_ 95 | if child.text: 96 | extras.append(child.text) 97 | elif child.tag in ('system-out', 'system-err') and child.text: 98 | if child.tag == 'system-out': 99 | heading = _('Captured stdout') 100 | else: 101 | heading = _('Captured stderr') 102 | contents = child.text.rstrip('\n') 103 | extras.append('----- {} -----\n{}'.format(heading, 104 | contents)) 105 | 106 | extra_text = '\n\n'.join(extras) 107 | testresults.append( 108 | TestResult(category, status, name, message, time, extra_text)) 109 | 110 | return testresults 111 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | main: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | OS: ['ubuntu', 'macos', 'windows'] 17 | PYTHON_VERSION: ['3.10', '3.11', '3.12'] 18 | SPYDER_SOURCE: ['conda', 'git'] 19 | exclude: 20 | - OS: ['macos', 'windows'] 21 | PYTHON_VERSION: ['3.11'] 22 | name: ${{ matrix.OS }} py${{ matrix.PYTHON_VERSION }} spyder-from-${{ matrix.SPYDER_SOURCE }} 23 | runs-on: ${{ matrix.OS }}-latest 24 | env: 25 | CI: True 26 | PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }} 27 | steps: 28 | - name: Checkout branch 29 | uses: actions/checkout@v4 30 | with: 31 | path: 'spyder-unittest' 32 | - name: Install System Packages 33 | if: matrix.OS == 'ubuntu' 34 | run: | 35 | sudo apt-get update --fix-missing 36 | sudo apt-get install -qq pyqt5-dev-tools libxcb-xinerama0 xterm --fix-missing 37 | - name: Install Conda 38 | uses: conda-incubator/setup-miniconda@v3 39 | with: 40 | miniforge-version: latest 41 | auto-update-conda: true 42 | conda-remove-defaults: "true" 43 | python-version: ${{ matrix.PYTHON_VERSION }} 44 | - name: Checkout Spyder from git 45 | if: matrix.SPYDER_SOURCE == 'git' 46 | uses: actions/checkout@v4 47 | with: 48 | repository: 'spyder-ide/spyder' 49 | path: 'spyder' 50 | - name: Install Spyder's dependencies (main) 51 | if: matrix.SPYDER_SOURCE == 'git' 52 | shell: bash -l {0} 53 | run: conda env update --file spyder/requirements/main.yml 54 | - name: Install Spyder's dependencies (Linux) 55 | if: matrix.SPYDER_SOURCE == 'git' && matrix.OS == 'ubuntu' 56 | shell: bash -l {0} 57 | run: conda env update --file spyder/requirements/linux.yml 58 | - name: Install Spyder's dependencies (Mac / Windows) 59 | if: matrix.SPYDER_SOURCE == 'git' && matrix.OS != 'ubuntu' 60 | shell: bash -l {0} 61 | run: conda env update --file spyder/requirements/${{ matrix.OS }}.yml 62 | - name: Install Spyder from source 63 | if: matrix.SPYDER_SOURCE == 'git' 64 | shell: bash -l {0} 65 | run: python -m pip install --no-deps spyder/ 66 | - name: Install plugin dependencies (without Spyder) 67 | if: matrix.SPYDER_SOURCE == 'git' 68 | shell: bash -l {0} 69 | run: | 70 | python spyder-unittest/.github/scripts/generate-without-spyder.py 71 | conda install --file spyder-unittest/requirements/without-spyder.txt -y 72 | - name: Install plugin dependencies 73 | if: matrix.SPYDER_SOURCE == 'conda' 74 | shell: bash -l {0} 75 | run: conda install --file spyder-unittest/requirements/conda.txt -y 76 | - name: Install test dependencies 77 | shell: bash -l {0} 78 | run: conda install --file spyder-unittest/requirements/tests.txt -y 79 | - name: Install plugin 80 | shell: bash -l {0} 81 | run: python -m pip install --no-deps spyder-unittest/ 82 | - name: Show environment information 83 | shell: bash -l {0} 84 | run: | 85 | conda info 86 | conda list 87 | - name: Run tests (Linux) 88 | if: matrix.OS == 'ubuntu' 89 | uses: nick-fields/retry@v3 90 | with: 91 | timeout_minutes: 10 92 | max_attempts: 3 93 | shell: bash 94 | command: | 95 | . ~/.profile 96 | xvfb-run --auto-servernum pytest spyder-unittest/spyder_unittest -vv 97 | - name: Run tests (MacOS) 98 | if: matrix.OS == 'macos' 99 | uses: nick-fields/retry@v3 100 | with: 101 | timeout_minutes: 10 102 | max_attempts: 3 103 | shell: bash 104 | command: | 105 | . ~/.profile 106 | pytest spyder-unittest/spyder_unittest -vv 107 | - name: Run tests (Windows) 108 | if: matrix.OS == 'windows' 109 | uses: nick-fields/retry@v3 110 | with: 111 | timeout_minutes: 10 112 | max_attempts: 3 113 | command: pytest spyder-unittest/spyder_unittest -vv 114 | -------------------------------------------------------------------------------- /spyder_unittest/backend/workers/unittestworker.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """ 7 | Script for running unittest tests. 8 | 9 | This script is meant to be run in a separate process by a UnittestRunner. 10 | It runs tests via the unittest framework and transmits the results over a ZMQ 11 | socket so that the UnittestRunner can read them. 12 | 13 | Usage: python unittestworker.py port [testname] 14 | 15 | Here, `port` is the port number of the ZMQ socket. Use `file` to store the 16 | results in the file `unittestworker.json`. The optional argument `testname` 17 | is the test to run; if omitted, run all tests. 18 | """ 19 | 20 | from __future__ import annotations 21 | 22 | # Standard library imports 23 | import os 24 | import sys 25 | from typing import ClassVar 26 | from unittest import ( 27 | defaultTestLoader, TestCase, TestSuite, TextTestResult, TextTestRunner) 28 | 29 | # Local imports 30 | # Note that the script can be run in an environment that does not contain 31 | # spyder_unittest so `from spyder_unittest.xxx import xxx` does not work. 32 | from zmqwriter import FileStub, ZmqStreamWriter 33 | 34 | 35 | class SpyderTestResult(TextTestResult): 36 | """ 37 | Store test results and write them to a ZmqStreamWriter. 38 | 39 | The member `.writer` should be set to a ZmqStreamWriter before 40 | running any tests. 41 | """ 42 | 43 | writer: ClassVar[ZmqStreamWriter] 44 | 45 | def startTest(self, test: TestCase) -> None: 46 | self.writer.write({ 47 | 'event': 'startTest', 48 | 'id': test.id() 49 | }) 50 | super().startTest(test) 51 | 52 | def addSuccess(self, test: TestCase) -> None: 53 | self.writer.write({ 54 | 'event': 'addSuccess', 55 | 'id': test.id() 56 | }) 57 | super().addSuccess(test) 58 | 59 | def addError(self, test: TestCase, err) -> None: 60 | (__, value, __) = err 61 | first_line = str(value).splitlines()[0] 62 | self.writer.write({ 63 | 'event': 'addError', 64 | 'id': test.id(), 65 | 'reason': f'{type(value).__name__}: {first_line}', 66 | 'err': self._exc_info_to_string(err, test) 67 | }) 68 | super().addError(test, err) 69 | 70 | def addFailure(self, test: TestCase, err) -> None: 71 | (__, value, __) = err 72 | first_line = str(value).splitlines()[0] 73 | self.writer.write({ 74 | 'event': 'addFailure', 75 | 'id': test.id(), 76 | 'reason': f'{type(value).__name__}: {first_line}', 77 | 'err': self._exc_info_to_string(err, test) 78 | }) 79 | super().addFailure(test, err) 80 | 81 | def addSkip(self, test: TestCase, reason: str) -> None: 82 | self.writer.write({ 83 | 'event': 'addSkip', 84 | 'id': test.id(), 85 | 'reason': reason 86 | }) 87 | super().addSkip(test, reason) 88 | 89 | def addExpectedFailure(self, test: TestCase, err) -> None: 90 | (__, value, __) = err 91 | first_line = str(value).splitlines()[0] 92 | self.writer.write({ 93 | 'event': 'addExpectedFailure', 94 | 'id': test.id(), 95 | 'reason': f'{type(value).__name__}: {first_line}', 96 | 'err': self._exc_info_to_string(err, test) 97 | }) 98 | super().addExpectedFailure(test, err) 99 | 100 | def addUnexpectedSuccess(self, test: TestCase) -> None: 101 | self.writer.write({ 102 | 'event': 'addUnexpectedSuccess', 103 | 'id': test.id() 104 | }) 105 | super().addUnexpectedSuccess(test) 106 | 107 | 108 | def report_collected(writer: ZmqStreamWriter, test_suite: TestSuite) -> None: 109 | for test in test_suite: 110 | if isinstance(test, TestSuite): 111 | report_collected(writer, test) 112 | else: 113 | writer.write({ 114 | 'event': 'collected', 115 | 'id': test.id() 116 | }) 117 | 118 | 119 | def main(args: list[str]) -> None: 120 | """Run unittest tests.""" 121 | # Parse first command line argument and create writer 122 | if args[1] != 'file': 123 | writer = ZmqStreamWriter(args[1]) 124 | else: 125 | writer = FileStub('unittestworker.log') 126 | SpyderTestResult.writer = writer 127 | 128 | # Gather tests 129 | if args[2:]: 130 | # Add cwd to path so that modules can be found 131 | sys.path = [os.getcwd()] + sys.path 132 | test_suite = defaultTestLoader.loadTestsFromNames(args[2:]) 133 | else: 134 | test_suite = defaultTestLoader.discover('.') 135 | report_collected(writer, test_suite) 136 | 137 | # Run tests 138 | test_runner = TextTestRunner(verbosity=2, resultclass=SpyderTestResult) 139 | test_runner.run(test_suite) 140 | writer.close() 141 | 142 | 143 | if __name__ == '__main__': 144 | main(sys.argv) 145 | -------------------------------------------------------------------------------- /spyder_unittest/backend/workers/pytestworker.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2017 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """ 7 | Script for running pytest tests. 8 | 9 | This script is meant to be run in a separate process by a PyTestRunner. 10 | It runs tests via the pytest framework and prints the results so that the 11 | PyTestRunner can read them. 12 | """ 13 | 14 | # Standard library imports 15 | import sys 16 | 17 | # Third party imports 18 | import pytest 19 | 20 | # Local imports 21 | # Note that the script can be run in an environment that does not contain 22 | # spyder_unittest so `from spyder_unittest.xxx import xxx` does not work. 23 | from zmqwriter import FileStub, ZmqStreamWriter 24 | 25 | 26 | class SpyderPlugin(): 27 | """Pytest plugin which reports in format suitable for Spyder.""" 28 | 29 | def __init__(self, writer): 30 | """Constructor.""" 31 | self.writer = writer 32 | 33 | def initialize_logreport(self): 34 | """Reset accumulator variables.""" 35 | self.status = '---' 36 | self.duration = 0 37 | self.longrepr = [] 38 | self.sections = [] 39 | self.had_error = False 40 | self.was_skipped = False 41 | self.was_xfail = False 42 | 43 | def pytest_report_header(self, config, startdir): 44 | """Called by pytest before any reporting.""" 45 | self.writer.write({ 46 | 'event': 'config', 47 | 'rootdir': str(config.rootdir) 48 | }) 49 | 50 | def pytest_collectreport(self, report): 51 | """Called by pytest after collecting tests from a file.""" 52 | if report.outcome == 'failed': 53 | self.writer.write({ 54 | 'event': 'collecterror', 55 | 'nodeid': report.nodeid, 56 | 'longrepr': str(report.longrepr) 57 | }) 58 | 59 | def pytest_itemcollected(self, item): 60 | """Called by pytest when a test item is collected.""" 61 | self.writer.write({ 62 | 'event': 'collected', 63 | 'nodeid': item.nodeid 64 | }) 65 | 66 | def pytest_runtest_logstart(self, nodeid, location): 67 | """Called by pytest before running a test.""" 68 | self.writer.write({ 69 | 'event': 'starttest', 70 | 'nodeid': nodeid 71 | }) 72 | self.initialize_logreport() 73 | 74 | def pytest_runtest_logreport(self, report): 75 | """Called by pytest when a phase of a test is completed.""" 76 | if report.when == 'call': 77 | self.status = report.outcome 78 | self.duration = report.duration 79 | else: 80 | if report.outcome == 'failed': 81 | self.had_error = True 82 | elif report.outcome == 'skipped': 83 | self.was_skipped = True 84 | if hasattr(report, 'wasxfail'): 85 | self.was_xfail = True 86 | self.longrepr.append(report.wasxfail if report.wasxfail else 87 | 'WAS EXPECTED TO FAIL') 88 | self.sections = report.sections # already accumulated over phases 89 | if report.longrepr: 90 | first_msg_idx = len(self.longrepr) 91 | if hasattr(report.longrepr, 'reprcrash'): 92 | self.longrepr.append(report.longrepr.reprcrash.message) 93 | if isinstance(report.longrepr, tuple): 94 | self.longrepr.append(report.longrepr[2]) 95 | elif isinstance(report.longrepr, str): 96 | self.longrepr.append(report.longrepr) 97 | else: 98 | self.longrepr.append(str(report.longrepr)) 99 | if report.outcome == 'failed' and report.when in ( 100 | 'setup', 'teardown'): 101 | self.longrepr[first_msg_idx] = '{} {}: {}'.format( 102 | 'ERROR at', report.when, self.longrepr[first_msg_idx]) 103 | 104 | def pytest_runtest_logfinish(self, nodeid, location): 105 | """Called by pytest when the entire test is completed.""" 106 | if self.was_xfail: 107 | if self.status == 'passed': 108 | self.status = 'xpassed' 109 | else: # 'skipped' 110 | self.status = 'xfailed' 111 | elif self.was_skipped: 112 | self.status = 'skipped' 113 | data = {'event': 'logreport', 114 | 'outcome': self.status, 115 | 'witherror': self.had_error, 116 | 'sections': self.sections, 117 | 'duration': self.duration, 118 | 'nodeid': nodeid, 119 | 'filename': location[0], 120 | 'lineno': location[1]} 121 | if self.longrepr: 122 | msg_lines = self.longrepr[0].rstrip().splitlines() 123 | data['message'] = msg_lines[0] 124 | start_item = 1 if len(msg_lines) == 1 else 0 125 | data['longrepr'] = '\n'.join(self.longrepr[start_item:]) 126 | self.writer.write(data) 127 | 128 | def main(args): 129 | """Run pytest with the Spyder plugin.""" 130 | if args[1] == 'file': 131 | writer = FileStub('pytestworker.log') 132 | else: 133 | writer = ZmqStreamWriter(int(args[1])) 134 | result = pytest.main(args[2:], plugins=[SpyderPlugin(writer)]) 135 | writer.close() 136 | return result 137 | 138 | 139 | if __name__ == '__main__': 140 | result = main(sys.argv) 141 | sys.exit(result) 142 | -------------------------------------------------------------------------------- /spyder_unittest/widgets/tests/test_configdialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Tests for configdialog.py.""" 7 | 8 | # Standard library imports 9 | import os 10 | 11 | # Third party imports 12 | from qtpy.QtWidgets import QDialogButtonBox 13 | 14 | # Local imports 15 | from spyder_unittest.widgets.configdialog import Config, ConfigDialog 16 | 17 | 18 | class SpamRunner: 19 | name = 'spam' 20 | 21 | @classmethod 22 | def is_installed(cls): 23 | return False 24 | 25 | 26 | class HamRunner: 27 | name = 'ham' 28 | 29 | @classmethod 30 | def is_installed(cls): 31 | return True 32 | 33 | 34 | class FakePytestRunner: 35 | name = 'pytest' 36 | 37 | @classmethod 38 | def is_installed(cls): 39 | return True 40 | 41 | 42 | frameworks = {r.name: r for r in [HamRunner, FakePytestRunner, SpamRunner]} 43 | 44 | versions = { 45 | 'spam': {'available': False}, 46 | 'ham': {'available': True}, 47 | 'pytest': {'available': True, 'plugins': {'pytest-cov', '3.1.4'}} 48 | } 49 | 50 | 51 | def default_config(): 52 | return Config(framework=None, wdir=os.getcwd(), coverage=False, args=[]) 53 | 54 | 55 | def test_configdialog_uses_frameworks(qtbot): 56 | configdialog = ConfigDialog( 57 | {'ham': HamRunner}, default_config(), versions) 58 | assert configdialog.framework_combobox.count() == 1 59 | assert configdialog.framework_combobox.itemText(0) == 'ham' 60 | 61 | 62 | def test_configdialog_indicates_unvailable_frameworks(qtbot): 63 | configdialog = ConfigDialog( 64 | {'spam': SpamRunner}, default_config(), versions) 65 | assert configdialog.framework_combobox.count() == 1 66 | assert configdialog.framework_combobox.itemText( 67 | 0) == 'spam (not available)' 68 | 69 | 70 | def test_configdialog_disables_unavailable_frameworks(qtbot): 71 | configdialog = ConfigDialog(frameworks, default_config(), versions) 72 | model = configdialog.framework_combobox.model() 73 | assert model.item(0).isEnabled() # ham 74 | assert model.item(1).isEnabled() # pytest 75 | assert not model.item(2).isEnabled() # spam 76 | 77 | 78 | def test_configdialog_sets_initial_config(qtbot): 79 | config = Config(framework='pytest', wdir='/some/dir', 80 | coverage=True, args=['some', 'arg']) 81 | configdialog = ConfigDialog(frameworks, config, versions) 82 | assert configdialog.get_config() == config 83 | 84 | 85 | def test_configdialog_click_pytest(qtbot): 86 | configdialog = ConfigDialog(frameworks, default_config(), versions) 87 | qtbot.addWidget(configdialog) 88 | configdialog.framework_combobox.setCurrentIndex(1) 89 | assert configdialog.get_config().framework == 'pytest' 90 | 91 | 92 | def test_configdialog_ok_initially_disabled(qtbot): 93 | configdialog = ConfigDialog(frameworks, default_config(), versions) 94 | qtbot.addWidget(configdialog) 95 | assert not configdialog.buttons.button(QDialogButtonBox.Ok).isEnabled() 96 | 97 | 98 | def test_configdialog_ok_setting_framework_initially_enables_ok(qtbot): 99 | config = Config(framework='ham', wdir=os.getcwd()) 100 | configdialog = ConfigDialog(frameworks, config, versions) 101 | qtbot.addWidget(configdialog) 102 | assert configdialog.buttons.button(QDialogButtonBox.Ok).isEnabled() 103 | 104 | 105 | def test_configdialog_clicking_pytest_enables_ok(qtbot): 106 | configdialog = ConfigDialog(frameworks, default_config(), versions) 107 | qtbot.addWidget(configdialog) 108 | configdialog.framework_combobox.setCurrentIndex(1) 109 | assert configdialog.buttons.button(QDialogButtonBox.Ok).isEnabled() 110 | 111 | 112 | def test_configdialog_coverage_checkbox(qtbot, monkeypatch): 113 | configdialog = ConfigDialog(frameworks, default_config(), versions) 114 | qtbot.addWidget(configdialog) 115 | configdialog.framework_combobox.setCurrentIndex(1) 116 | configdialog.coverage_checkbox.click() 117 | assert configdialog.get_config().coverage is True 118 | 119 | 120 | def test_configdialog_coverage_checkbox_pytestcov_noinstall(qtbot, monkeypatch): 121 | local_versions = versions.copy() 122 | local_versions['pytest']['plugins'] = {} 123 | configdialog = ConfigDialog(frameworks, default_config(), local_versions) 124 | qtbot.addWidget(configdialog) 125 | configdialog.framework_combobox.setCurrentIndex(1) 126 | assert configdialog.coverage_checkbox.isEnabled() is False 127 | 128 | 129 | def test_configdialog_args_lineedit(qtbot): 130 | configdialog = ConfigDialog(frameworks, default_config(), versions) 131 | qtbot.addWidget(configdialog) 132 | configdialog.args_lineedit.setText('-x "ham and" spam') 133 | assert configdialog.get_config().args == ['-x', 'ham and', 'spam'] 134 | 135 | 136 | def test_configdialog_wdir_lineedit(qtbot): 137 | configdialog = ConfigDialog(frameworks, default_config(), versions) 138 | qtbot.addWidget(configdialog) 139 | wdir = os.path.normpath(os.path.join(os.getcwd(), os.path.pardir)) 140 | configdialog.wdir_lineedit.setText(wdir) 141 | assert configdialog.get_config().wdir == wdir 142 | 143 | 144 | def test_configdialog_wdir_button(qtbot, monkeypatch): 145 | configdialog = ConfigDialog(frameworks, default_config(), versions) 146 | qtbot.addWidget(configdialog) 147 | wdir = os.path.normpath(os.path.join(os.getcwd(), os.path.pardir)) 148 | monkeypatch.setattr( 149 | 'spyder_unittest.widgets.configdialog.getexistingdirectory', 150 | lambda parent, caption, basedir: wdir) 151 | configdialog.wdir_button.click() 152 | assert configdialog.get_config().wdir == wdir 153 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spyder-Unittest 2 | 3 | ## Project information 4 | [![license](https://img.shields.io/pypi/l/spyder-unittest.svg)](./LICENSE) 5 | [![conda version](https://img.shields.io/conda/v/spyder-ide/spyder-unittest.svg)](https://www.anaconda.com/download/) 6 | [![download count](https://img.shields.io/conda/d/spyder-ide/spyder-unittest.svg)](https://www.anaconda.com/download/) 7 | [![pypi version](https://img.shields.io/pypi/v/spyder-unittest.svg)](https://pypi.org/project/spyder-unittest/) 8 | [![Join the chat at https://gitter.im/spyder-ide/public](https://badges.gitter.im/spyder-ide/spyder.svg)](https://gitter.im/spyder-ide/public) 9 | [![OpenCollective Backers](https://opencollective.com/spyder/backers/badge.svg?color=blue)](#backers) 10 | [![OpenCollective Sponsors](https://opencollective.com/spyder/sponsors/badge.svg?color=blue)](#sponsors) 11 | 12 | ## Build status 13 | [![Windows status](https://github.com/spyder-ide/spyder-unittest/workflows/Windows%20tests/badge.svg)](https://github.com/spyder-ide/spyder-notebook/actions?query=workflow%3A%22Windows+tests%22) 14 | [![Linux status](https://github.com/spyder-ide/spyder-unittest/workflows/Linux%20tests/badge.svg)](https://github.com/spyder-ide/spyder-notebook/actions?query=workflow%3A%22Linux+tests%22) 15 | [![MacOS status](https://github.com/spyder-ide/spyder-unittest/workflows/Macos%20tests/badge.svg)](https://github.com/spyder-ide/spyder-notebook/actions?query=workflow%3A%22Macos+tests%22) 16 | [![Crowdin](https://badges.crowdin.net/spyder-unittest/localized.svg)](https://crowdin.com/project/spyder-unittest) 17 | 18 | *Copyright © 2014 Spyder Project Contributors* 19 | 20 | ![Screenshot of spyder-unittest plugin showing test results](./doc/screenshot.png) 21 | 22 | ## Description 23 | 24 | Spyder-unittest is a plugin that integrates popular unit test frameworks 25 | with Spyder, allowing you to run test suites and view the results in the IDE. 26 | 27 | The plugin supports the `unittest` module in the Python standard library 28 | as well as the `pytest` and `nose2` testing frameworks. 29 | Support for `pytest` is most complete at the moment. 30 | 31 | 32 | ## Installation 33 | 34 | To install this plugin, you can use either ``pip`` or ``conda`` package managers, as follows: 35 | 36 | Using conda (the recommended way!): 37 | 38 | ``` 39 | conda install spyder-unittest -c conda-forge 40 | ``` 41 | 42 | Using pip: 43 | 44 | ``` 45 | pip install spyder-unittest 46 | ``` 47 | 48 | **Note**: At the moment it is not possible to use this plugin with the [Spyder installers](http://docs.spyder-ide.org/current/installation.html#standalone-installers) for Windows and macOS. We're working to make that a reality in the future. 49 | 50 | 51 | ## Usage 52 | 53 | The plugin adds an item `Run unit tests` to the `Run` menu in Spyder. 54 | Click on this to run the unit tests. After you specify the testing framework 55 | and the directory under which the tests are stored, the tests are run. 56 | The `Unit testing` window pane (displayed at the top of this file) will pop up 57 | with the results. If you are using `pytest`, you can double-click on a test 58 | to view it in the editor. 59 | 60 | If you want to run tests in a different directory or switch testing 61 | frameworks, click `Configure` in the Options menu (cogwheel icon), 62 | which is located in the upper right corner of the `Unit testing` pane. 63 | 64 | 65 | ## Feedback 66 | 67 | Bug reports, feature requests and other ideas are more than welcome on the 68 | [issue tracker](https://github.com/spyder-ide/spyder-unittest/issues). 69 | Use the [Spyder Google Group](https://groups.google.com/group/spyderlib) 70 | or our [Gitter Chatroom](https://gitter.im/spyder-ide/public) 71 | for general discussion. 72 | 73 | 74 | ## Development 75 | 76 | Development of the plugin is done at https://github.com/spyder-ide/spyder-unittest . 77 | You can install the development version of the plugin by cloning the git repository 78 | and running `pip install .`, possibly with the `--editable` flag. 79 | 80 | The plugin has the following dependencies: 81 | 82 | * [spyder](https://github.com/spyder-ide/spyder) (obviously), at least version 4.0 83 | * [lxml](http://lxml.de/) 84 | * the testing framework that you will be using: [pytest](https://pytest.org) 85 | and/or [nose2](https://docs.nose2.io) 86 | 87 | In order to run the tests distributed with this plugin, you need 88 | [nose2](https://docs.nose2.io), [pytest](https://pytest.org) 89 | and [pytest-qt](https://github.com/pytest-dev/pytest-qt). If you use Python 2, 90 | you also need [mock](https://github.com/testing-cabal/mock). 91 | 92 | You are very welcome to submit code contributions in the form of pull 93 | requests to the 94 | [issue tracker](https://github.com/spyder-ide/spyder-unittest/issues). 95 | GitHub is configured to run pull requests automatically against the test suite 96 | and against several automatic style checkers using 97 | [ciocheck](https://github.com/ContinuumIO/ciocheck). 98 | The style checkers can be rather finicky so you may want to install ciocheck 99 | locally and run them before submitting the code. 100 | 101 | 102 | ## Contributing 103 | 104 | Everyone is welcome to contribute! The document [Contributing to Spyder]( 105 | https://github.com/spyder-ide/spyder/blob/master/CONTRIBUTING.md) 106 | also applies to the unittest plugin. 107 | 108 | We are grateful to the entire Spyder community for their support, without which 109 | this plugin and the whole of Spyder would be a lot less awesome. 110 | 111 | 112 | ## More information 113 | 114 | [Main Website](https://www.spyder-ide.org/) 115 | 116 | [Download Spyder (with Anaconda)](https://www.anaconda.com/download/) 117 | 118 | [Spyder Github](https://github.com/spyder-ide/spyder) 119 | 120 | [Troubleshooting Guide and FAQ]( 121 | https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) 122 | 123 | [Development Wiki](https://github.com/spyder-ide/spyder/wiki/Dev:-Index) 124 | 125 | [Gitter Chatroom](https://gitter.im/spyder-ide/public) 126 | 127 | [Google Group](https://groups.google.com/group/spyderlib) 128 | 129 | [@Spyder_IDE on Twitter](https://twitter.com/spyder_ide) 130 | 131 | [@SpyderIDE on Facebook](https://www.facebook.com/SpyderIDE/) 132 | 133 | [Support Spyder on OpenCollective](https://opencollective.com/spyder/) 134 | -------------------------------------------------------------------------------- /spyder_unittest/locale/spyder_unittest.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR ORGANIZATION 3 | # FIRST AUTHOR , YEAR. 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PACKAGE VERSION\n" 8 | "POT-Creation-Date: 2023-06-23 16:41+0100\n" 9 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 10 | "Last-Translator: FULL NAME \n" 11 | "Language-Team: LANGUAGE \n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=UTF-8\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Generated-By: pygettext.py 1.5\n" 16 | 17 | #: spyder_unittest/backend/nose2runner.py:99 18 | msgid "Captured stdout" 19 | msgstr "" 20 | 21 | #: spyder_unittest/backend/nose2runner.py:101 22 | msgid "Captured stderr" 23 | msgstr "" 24 | 25 | #: spyder_unittest/backend/pytestrunner.py:129 26 | msgid "(none)" 27 | msgstr "" 28 | 29 | #: spyder_unittest/backend/pytestrunner.py:129 30 | msgid "Missing: {}" 31 | msgstr "" 32 | 33 | #: spyder_unittest/backend/pytestrunner.py:130 34 | msgid "" 35 | "{}\n" 36 | "{}" 37 | msgstr "" 38 | 39 | #: spyder_unittest/unittestplugin.py:73 spyder_unittest/widgets/unittestgui.py:138 40 | msgid "Unit testing" 41 | msgstr "" 42 | 43 | #: spyder_unittest/unittestplugin.py:84 44 | msgid "Run test suites and view their results" 45 | msgstr "" 46 | 47 | #: spyder_unittest/unittestplugin.py:105 spyder_unittest/unittestplugin.py:106 spyder_unittest/widgets/unittestgui.py:424 48 | msgid "Run unit tests" 49 | msgstr "" 50 | 51 | #: spyder_unittest/widgets/configdialog.py:78 52 | msgid "Configure tests" 53 | msgstr "" 54 | 55 | #: spyder_unittest/widgets/configdialog.py:86 56 | msgid "Test framework:" 57 | msgstr "" 58 | 59 | #: spyder_unittest/widgets/configdialog.py:95 spyder_unittest/widgets/unittestgui.py:294 60 | msgid "not available" 61 | msgstr "" 62 | 63 | #: spyder_unittest/widgets/configdialog.py:102 64 | msgid "Command-line arguments:" 65 | msgstr "" 66 | 67 | #: spyder_unittest/widgets/configdialog.py:106 68 | msgid "Extra command-line arguments when running tests" 69 | msgstr "" 70 | 71 | #: spyder_unittest/widgets/configdialog.py:118 72 | msgid "Include coverage report in output" 73 | msgstr "" 74 | 75 | #: spyder_unittest/widgets/configdialog.py:119 76 | msgid "Works only for pytest, requires pytest-cov" 77 | msgstr "" 78 | 79 | #: spyder_unittest/widgets/configdialog.py:131 80 | msgid "Directory from which to run tests:" 81 | msgstr "" 82 | 83 | #: spyder_unittest/widgets/configdialog.py:137 spyder_unittest/widgets/configdialog.py:196 84 | msgid "Select directory" 85 | msgstr "" 86 | 87 | #: spyder_unittest/widgets/confpage.py:26 88 | msgid "Settings" 89 | msgstr "" 90 | 91 | #: spyder_unittest/widgets/confpage.py:28 92 | msgid "Abbreviate test names" 93 | msgstr "" 94 | 95 | #: spyder_unittest/widgets/datatree.py:45 96 | msgid "Message" 97 | msgstr "" 98 | 99 | #: spyder_unittest/widgets/datatree.py:45 100 | msgid "Name" 101 | msgstr "" 102 | 103 | #: spyder_unittest/widgets/datatree.py:45 104 | msgid "Status" 105 | msgstr "" 106 | 107 | #: spyder_unittest/widgets/datatree.py:45 108 | msgid "Time (ms)" 109 | msgstr "" 110 | 111 | #: spyder_unittest/widgets/datatree.py:153 112 | msgid "Collapse" 113 | msgstr "" 114 | 115 | #: spyder_unittest/widgets/datatree.py:156 116 | msgid "Expand" 117 | msgstr "" 118 | 119 | #: spyder_unittest/widgets/datatree.py:162 120 | msgid "Go to definition" 121 | msgstr "" 122 | 123 | #: spyder_unittest/widgets/datatree.py:169 124 | msgid "Run only this test" 125 | msgstr "" 126 | 127 | #: spyder_unittest/widgets/datatree.py:421 128 | msgid "test" 129 | msgstr "" 130 | 131 | #: spyder_unittest/widgets/datatree.py:421 132 | msgid "tests" 133 | msgstr "" 134 | 135 | #: spyder_unittest/widgets/datatree.py:425 136 | msgid "No results to show." 137 | msgstr "" 138 | 139 | #: spyder_unittest/widgets/datatree.py:430 140 | msgid "collected {}" 141 | msgstr "" 142 | 143 | #: spyder_unittest/widgets/datatree.py:431 144 | msgid "{} failed" 145 | msgstr "" 146 | 147 | #: spyder_unittest/widgets/datatree.py:432 148 | msgid ", {} passed" 149 | msgstr "" 150 | 151 | #: spyder_unittest/widgets/datatree.py:434 152 | msgid ", {} other" 153 | msgstr "" 154 | 155 | #: spyder_unittest/widgets/datatree.py:436 156 | msgid ", {} pending" 157 | msgstr "" 158 | 159 | #: spyder_unittest/widgets/datatree.py:441 160 | msgid ", {} coverage" 161 | msgstr "" 162 | 163 | #: spyder_unittest/widgets/unittestgui.py:151 164 | msgid "Configure ..." 165 | msgstr "" 166 | 167 | #: spyder_unittest/widgets/unittestgui.py:158 168 | msgid "Show output" 169 | msgstr "" 170 | 171 | #: spyder_unittest/widgets/unittestgui.py:165 172 | msgid "Collapse all" 173 | msgstr "" 174 | 175 | #: spyder_unittest/widgets/unittestgui.py:172 176 | msgid "Expand all" 177 | msgstr "" 178 | 179 | #: spyder_unittest/widgets/unittestgui.py:179 spyder_unittest/widgets/unittestgui.py:300 180 | msgid "Dependencies" 181 | msgstr "" 182 | 183 | #: spyder_unittest/widgets/unittestgui.py:248 184 | msgid "Unit testing output" 185 | msgstr "" 186 | 187 | #: spyder_unittest/widgets/unittestgui.py:291 188 | msgid "Versions of frameworks and their installed plugins:" 189 | msgstr "" 190 | 191 | #: spyder_unittest/widgets/unittestgui.py:393 192 | msgid "Error" 193 | msgstr "" 194 | 195 | #: spyder_unittest/widgets/unittestgui.py:393 196 | msgid "Process failed to start" 197 | msgstr "" 198 | 199 | #: spyder_unittest/widgets/unittestgui.py:396 200 | msgid "Running tests ..." 201 | msgstr "" 202 | 203 | #: spyder_unittest/widgets/unittestgui.py:417 204 | msgid "Stop" 205 | msgstr "" 206 | 207 | #: spyder_unittest/widgets/unittestgui.py:418 208 | msgid "Stop current test process" 209 | msgstr "" 210 | 211 | #: spyder_unittest/widgets/unittestgui.py:423 212 | msgid "Run tests" 213 | msgstr "" 214 | 215 | #: spyder_unittest/widgets/unittestgui.py:451 216 | msgid "Test process exited abnormally" 217 | msgstr "" 218 | 219 | #: spyder_unittest/widgets/unittestgui.py:460 220 | msgid "not run" 221 | msgstr "" 222 | 223 | #: spyder_unittest/widgets/unittestgui.py:467 spyder_unittest/widgets/unittestgui.py:473 224 | msgid "pending" 225 | msgstr "" 226 | 227 | #: spyder_unittest/widgets/unittestgui.py:474 228 | msgid "running" 229 | msgstr "" 230 | 231 | #: spyder_unittest/widgets/unittestgui.py:480 232 | msgid "failure" 233 | msgstr "" 234 | 235 | #: spyder_unittest/widgets/unittestgui.py:481 236 | msgid "collection error" 237 | msgstr "" 238 | -------------------------------------------------------------------------------- /spyder_unittest/locale/ja/LC_MESSAGES/spyder_unittest.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: spyder-unittest\n" 4 | "POT-Creation-Date: 2023-06-23 16:41+0100\n" 5 | "PO-Revision-Date: 2023-06-23 20:17\n" 6 | "Last-Translator: \n" 7 | "Language-Team: Japanese\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Generated-By: pygettext.py 1.5\n" 12 | "Plural-Forms: nplurals=1; plural=0;\n" 13 | "X-Crowdin-Project: spyder-unittest\n" 14 | "X-Crowdin-Project-ID: 381839\n" 15 | "X-Crowdin-Language: ja\n" 16 | "X-Crowdin-File: /master/spyder_unittest/locale/spyder_unittest.pot\n" 17 | "X-Crowdin-File-ID: 49\n" 18 | "Language: ja_JP\n" 19 | 20 | #: spyder_unittest/backend/nose2runner.py:99 21 | msgid "Captured stdout" 22 | msgstr "" 23 | 24 | #: spyder_unittest/backend/nose2runner.py:101 25 | msgid "Captured stderr" 26 | msgstr "" 27 | 28 | #: spyder_unittest/backend/pytestrunner.py:129 29 | msgid "(none)" 30 | msgstr "" 31 | 32 | #: spyder_unittest/backend/pytestrunner.py:129 33 | msgid "Missing: {}" 34 | msgstr "" 35 | 36 | #: spyder_unittest/backend/pytestrunner.py:130 37 | msgid "{}\n" 38 | "{}" 39 | msgstr "" 40 | 41 | #: spyder_unittest/unittestplugin.py:73 spyder_unittest/widgets/unittestgui.py:138 42 | msgid "Unit testing" 43 | msgstr "" 44 | 45 | #: spyder_unittest/unittestplugin.py:84 46 | msgid "Run test suites and view their results" 47 | msgstr "" 48 | 49 | #: spyder_unittest/unittestplugin.py:105 spyder_unittest/unittestplugin.py:106 spyder_unittest/widgets/unittestgui.py:424 50 | msgid "Run unit tests" 51 | msgstr "" 52 | 53 | #: spyder_unittest/widgets/configdialog.py:78 54 | msgid "Configure tests" 55 | msgstr "" 56 | 57 | #: spyder_unittest/widgets/configdialog.py:86 58 | msgid "Test framework:" 59 | msgstr "" 60 | 61 | #: spyder_unittest/widgets/configdialog.py:95 spyder_unittest/widgets/unittestgui.py:294 62 | msgid "not available" 63 | msgstr "" 64 | 65 | #: spyder_unittest/widgets/configdialog.py:102 66 | msgid "Command-line arguments:" 67 | msgstr "" 68 | 69 | #: spyder_unittest/widgets/configdialog.py:106 70 | msgid "Extra command-line arguments when running tests" 71 | msgstr "" 72 | 73 | #: spyder_unittest/widgets/configdialog.py:118 74 | msgid "Include coverage report in output" 75 | msgstr "" 76 | 77 | #: spyder_unittest/widgets/configdialog.py:119 78 | msgid "Works only for pytest, requires pytest-cov" 79 | msgstr "" 80 | 81 | #: spyder_unittest/widgets/configdialog.py:131 82 | msgid "Directory from which to run tests:" 83 | msgstr "" 84 | 85 | #: spyder_unittest/widgets/configdialog.py:137 spyder_unittest/widgets/configdialog.py:196 86 | msgid "Select directory" 87 | msgstr "" 88 | 89 | #: spyder_unittest/widgets/confpage.py:26 90 | msgid "Settings" 91 | msgstr "" 92 | 93 | #: spyder_unittest/widgets/confpage.py:28 94 | msgid "Abbreviate test names" 95 | msgstr "" 96 | 97 | #: spyder_unittest/widgets/datatree.py:45 98 | msgid "Message" 99 | msgstr "" 100 | 101 | #: spyder_unittest/widgets/datatree.py:45 102 | msgid "Name" 103 | msgstr "" 104 | 105 | #: spyder_unittest/widgets/datatree.py:45 106 | msgid "Status" 107 | msgstr "" 108 | 109 | #: spyder_unittest/widgets/datatree.py:45 110 | msgid "Time (ms)" 111 | msgstr "" 112 | 113 | #: spyder_unittest/widgets/datatree.py:153 114 | msgid "Collapse" 115 | msgstr "" 116 | 117 | #: spyder_unittest/widgets/datatree.py:156 118 | msgid "Expand" 119 | msgstr "" 120 | 121 | #: spyder_unittest/widgets/datatree.py:162 122 | msgid "Go to definition" 123 | msgstr "" 124 | 125 | #: spyder_unittest/widgets/datatree.py:169 126 | msgid "Run only this test" 127 | msgstr "" 128 | 129 | #: spyder_unittest/widgets/datatree.py:421 130 | msgid "test" 131 | msgstr "" 132 | 133 | #: spyder_unittest/widgets/datatree.py:421 134 | msgid "tests" 135 | msgstr "" 136 | 137 | #: spyder_unittest/widgets/datatree.py:425 138 | msgid "No results to show." 139 | msgstr "" 140 | 141 | #: spyder_unittest/widgets/datatree.py:430 142 | msgid "collected {}" 143 | msgstr "" 144 | 145 | #: spyder_unittest/widgets/datatree.py:431 146 | msgid "{} failed" 147 | msgstr "" 148 | 149 | #: spyder_unittest/widgets/datatree.py:432 150 | msgid ", {} passed" 151 | msgstr "" 152 | 153 | #: spyder_unittest/widgets/datatree.py:434 154 | msgid ", {} other" 155 | msgstr "" 156 | 157 | #: spyder_unittest/widgets/datatree.py:436 158 | msgid ", {} pending" 159 | msgstr "" 160 | 161 | #: spyder_unittest/widgets/datatree.py:441 162 | msgid ", {} coverage" 163 | msgstr "" 164 | 165 | #: spyder_unittest/widgets/unittestgui.py:151 166 | msgid "Configure ..." 167 | msgstr "" 168 | 169 | #: spyder_unittest/widgets/unittestgui.py:158 170 | msgid "Show output" 171 | msgstr "" 172 | 173 | #: spyder_unittest/widgets/unittestgui.py:165 174 | msgid "Collapse all" 175 | msgstr "" 176 | 177 | #: spyder_unittest/widgets/unittestgui.py:172 178 | msgid "Expand all" 179 | msgstr "" 180 | 181 | #: spyder_unittest/widgets/unittestgui.py:179 spyder_unittest/widgets/unittestgui.py:300 182 | msgid "Dependencies" 183 | msgstr "" 184 | 185 | #: spyder_unittest/widgets/unittestgui.py:248 186 | msgid "Unit testing output" 187 | msgstr "" 188 | 189 | #: spyder_unittest/widgets/unittestgui.py:291 190 | msgid "Versions of frameworks and their installed plugins:" 191 | msgstr "" 192 | 193 | #: spyder_unittest/widgets/unittestgui.py:393 194 | msgid "Error" 195 | msgstr "" 196 | 197 | #: spyder_unittest/widgets/unittestgui.py:393 198 | msgid "Process failed to start" 199 | msgstr "" 200 | 201 | #: spyder_unittest/widgets/unittestgui.py:396 202 | msgid "Running tests ..." 203 | msgstr "" 204 | 205 | #: spyder_unittest/widgets/unittestgui.py:417 206 | msgid "Stop" 207 | msgstr "" 208 | 209 | #: spyder_unittest/widgets/unittestgui.py:418 210 | msgid "Stop current test process" 211 | msgstr "" 212 | 213 | #: spyder_unittest/widgets/unittestgui.py:423 214 | msgid "Run tests" 215 | msgstr "" 216 | 217 | #: spyder_unittest/widgets/unittestgui.py:451 218 | msgid "Test process exited abnormally" 219 | msgstr "" 220 | 221 | #: spyder_unittest/widgets/unittestgui.py:460 222 | msgid "not run" 223 | msgstr "" 224 | 225 | #: spyder_unittest/widgets/unittestgui.py:467 spyder_unittest/widgets/unittestgui.py:473 226 | msgid "pending" 227 | msgstr "" 228 | 229 | #: spyder_unittest/widgets/unittestgui.py:474 230 | msgid "running" 231 | msgstr "" 232 | 233 | #: spyder_unittest/widgets/unittestgui.py:480 234 | msgid "failure" 235 | msgstr "" 236 | 237 | #: spyder_unittest/widgets/unittestgui.py:481 238 | msgid "collection error" 239 | msgstr "" 240 | 241 | -------------------------------------------------------------------------------- /spyder_unittest/locale/es/LC_MESSAGES/spyder_unittest.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: spyder-unittest\n" 4 | "POT-Creation-Date: 2023-06-23 16:41+0100\n" 5 | "PO-Revision-Date: 2023-06-23 20:17\n" 6 | "Last-Translator: \n" 7 | "Language-Team: Spanish\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Generated-By: pygettext.py 1.5\n" 12 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 13 | "X-Crowdin-Project: spyder-unittest\n" 14 | "X-Crowdin-Project-ID: 381839\n" 15 | "X-Crowdin-Language: es-ES\n" 16 | "X-Crowdin-File: /master/spyder_unittest/locale/spyder_unittest.pot\n" 17 | "X-Crowdin-File-ID: 49\n" 18 | "Language: es_ES\n" 19 | 20 | #: spyder_unittest/backend/nose2runner.py:99 21 | msgid "Captured stdout" 22 | msgstr "" 23 | 24 | #: spyder_unittest/backend/nose2runner.py:101 25 | msgid "Captured stderr" 26 | msgstr "" 27 | 28 | #: spyder_unittest/backend/pytestrunner.py:129 29 | msgid "(none)" 30 | msgstr "" 31 | 32 | #: spyder_unittest/backend/pytestrunner.py:129 33 | msgid "Missing: {}" 34 | msgstr "" 35 | 36 | #: spyder_unittest/backend/pytestrunner.py:130 37 | msgid "{}\n" 38 | "{}" 39 | msgstr "" 40 | 41 | #: spyder_unittest/unittestplugin.py:73 spyder_unittest/widgets/unittestgui.py:138 42 | msgid "Unit testing" 43 | msgstr "" 44 | 45 | #: spyder_unittest/unittestplugin.py:84 46 | msgid "Run test suites and view their results" 47 | msgstr "" 48 | 49 | #: spyder_unittest/unittestplugin.py:105 spyder_unittest/unittestplugin.py:106 spyder_unittest/widgets/unittestgui.py:424 50 | msgid "Run unit tests" 51 | msgstr "" 52 | 53 | #: spyder_unittest/widgets/configdialog.py:78 54 | msgid "Configure tests" 55 | msgstr "" 56 | 57 | #: spyder_unittest/widgets/configdialog.py:86 58 | msgid "Test framework:" 59 | msgstr "" 60 | 61 | #: spyder_unittest/widgets/configdialog.py:95 spyder_unittest/widgets/unittestgui.py:294 62 | msgid "not available" 63 | msgstr "" 64 | 65 | #: spyder_unittest/widgets/configdialog.py:102 66 | msgid "Command-line arguments:" 67 | msgstr "" 68 | 69 | #: spyder_unittest/widgets/configdialog.py:106 70 | msgid "Extra command-line arguments when running tests" 71 | msgstr "" 72 | 73 | #: spyder_unittest/widgets/configdialog.py:118 74 | msgid "Include coverage report in output" 75 | msgstr "" 76 | 77 | #: spyder_unittest/widgets/configdialog.py:119 78 | msgid "Works only for pytest, requires pytest-cov" 79 | msgstr "" 80 | 81 | #: spyder_unittest/widgets/configdialog.py:131 82 | msgid "Directory from which to run tests:" 83 | msgstr "" 84 | 85 | #: spyder_unittest/widgets/configdialog.py:137 spyder_unittest/widgets/configdialog.py:196 86 | msgid "Select directory" 87 | msgstr "" 88 | 89 | #: spyder_unittest/widgets/confpage.py:26 90 | msgid "Settings" 91 | msgstr "" 92 | 93 | #: spyder_unittest/widgets/confpage.py:28 94 | msgid "Abbreviate test names" 95 | msgstr "" 96 | 97 | #: spyder_unittest/widgets/datatree.py:45 98 | msgid "Message" 99 | msgstr "" 100 | 101 | #: spyder_unittest/widgets/datatree.py:45 102 | msgid "Name" 103 | msgstr "" 104 | 105 | #: spyder_unittest/widgets/datatree.py:45 106 | msgid "Status" 107 | msgstr "" 108 | 109 | #: spyder_unittest/widgets/datatree.py:45 110 | msgid "Time (ms)" 111 | msgstr "" 112 | 113 | #: spyder_unittest/widgets/datatree.py:153 114 | msgid "Collapse" 115 | msgstr "" 116 | 117 | #: spyder_unittest/widgets/datatree.py:156 118 | msgid "Expand" 119 | msgstr "" 120 | 121 | #: spyder_unittest/widgets/datatree.py:162 122 | msgid "Go to definition" 123 | msgstr "" 124 | 125 | #: spyder_unittest/widgets/datatree.py:169 126 | msgid "Run only this test" 127 | msgstr "" 128 | 129 | #: spyder_unittest/widgets/datatree.py:421 130 | msgid "test" 131 | msgstr "" 132 | 133 | #: spyder_unittest/widgets/datatree.py:421 134 | msgid "tests" 135 | msgstr "" 136 | 137 | #: spyder_unittest/widgets/datatree.py:425 138 | msgid "No results to show." 139 | msgstr "" 140 | 141 | #: spyder_unittest/widgets/datatree.py:430 142 | msgid "collected {}" 143 | msgstr "" 144 | 145 | #: spyder_unittest/widgets/datatree.py:431 146 | msgid "{} failed" 147 | msgstr "" 148 | 149 | #: spyder_unittest/widgets/datatree.py:432 150 | msgid ", {} passed" 151 | msgstr "" 152 | 153 | #: spyder_unittest/widgets/datatree.py:434 154 | msgid ", {} other" 155 | msgstr "" 156 | 157 | #: spyder_unittest/widgets/datatree.py:436 158 | msgid ", {} pending" 159 | msgstr "" 160 | 161 | #: spyder_unittest/widgets/datatree.py:441 162 | msgid ", {} coverage" 163 | msgstr "" 164 | 165 | #: spyder_unittest/widgets/unittestgui.py:151 166 | msgid "Configure ..." 167 | msgstr "" 168 | 169 | #: spyder_unittest/widgets/unittestgui.py:158 170 | msgid "Show output" 171 | msgstr "" 172 | 173 | #: spyder_unittest/widgets/unittestgui.py:165 174 | msgid "Collapse all" 175 | msgstr "" 176 | 177 | #: spyder_unittest/widgets/unittestgui.py:172 178 | msgid "Expand all" 179 | msgstr "" 180 | 181 | #: spyder_unittest/widgets/unittestgui.py:179 spyder_unittest/widgets/unittestgui.py:300 182 | msgid "Dependencies" 183 | msgstr "" 184 | 185 | #: spyder_unittest/widgets/unittestgui.py:248 186 | msgid "Unit testing output" 187 | msgstr "" 188 | 189 | #: spyder_unittest/widgets/unittestgui.py:291 190 | msgid "Versions of frameworks and their installed plugins:" 191 | msgstr "" 192 | 193 | #: spyder_unittest/widgets/unittestgui.py:393 194 | msgid "Error" 195 | msgstr "" 196 | 197 | #: spyder_unittest/widgets/unittestgui.py:393 198 | msgid "Process failed to start" 199 | msgstr "" 200 | 201 | #: spyder_unittest/widgets/unittestgui.py:396 202 | msgid "Running tests ..." 203 | msgstr "" 204 | 205 | #: spyder_unittest/widgets/unittestgui.py:417 206 | msgid "Stop" 207 | msgstr "" 208 | 209 | #: spyder_unittest/widgets/unittestgui.py:418 210 | msgid "Stop current test process" 211 | msgstr "" 212 | 213 | #: spyder_unittest/widgets/unittestgui.py:423 214 | msgid "Run tests" 215 | msgstr "" 216 | 217 | #: spyder_unittest/widgets/unittestgui.py:451 218 | msgid "Test process exited abnormally" 219 | msgstr "" 220 | 221 | #: spyder_unittest/widgets/unittestgui.py:460 222 | msgid "not run" 223 | msgstr "" 224 | 225 | #: spyder_unittest/widgets/unittestgui.py:467 spyder_unittest/widgets/unittestgui.py:473 226 | msgid "pending" 227 | msgstr "" 228 | 229 | #: spyder_unittest/widgets/unittestgui.py:474 230 | msgid "running" 231 | msgstr "" 232 | 233 | #: spyder_unittest/widgets/unittestgui.py:480 234 | msgid "failure" 235 | msgstr "" 236 | 237 | #: spyder_unittest/widgets/unittestgui.py:481 238 | msgid "collection error" 239 | msgstr "" 240 | 241 | -------------------------------------------------------------------------------- /spyder_unittest/locale/hu/LC_MESSAGES/spyder_unittest.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: spyder-unittest\n" 4 | "POT-Creation-Date: 2023-06-23 16:41+0100\n" 5 | "PO-Revision-Date: 2023-06-23 20:17\n" 6 | "Last-Translator: \n" 7 | "Language-Team: Hungarian\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Generated-By: pygettext.py 1.5\n" 12 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 13 | "X-Crowdin-Project: spyder-unittest\n" 14 | "X-Crowdin-Project-ID: 381839\n" 15 | "X-Crowdin-Language: hu\n" 16 | "X-Crowdin-File: /master/spyder_unittest/locale/spyder_unittest.pot\n" 17 | "X-Crowdin-File-ID: 49\n" 18 | "Language: hu_HU\n" 19 | 20 | #: spyder_unittest/backend/nose2runner.py:99 21 | msgid "Captured stdout" 22 | msgstr "" 23 | 24 | #: spyder_unittest/backend/nose2runner.py:101 25 | msgid "Captured stderr" 26 | msgstr "" 27 | 28 | #: spyder_unittest/backend/pytestrunner.py:129 29 | msgid "(none)" 30 | msgstr "" 31 | 32 | #: spyder_unittest/backend/pytestrunner.py:129 33 | msgid "Missing: {}" 34 | msgstr "" 35 | 36 | #: spyder_unittest/backend/pytestrunner.py:130 37 | msgid "{}\n" 38 | "{}" 39 | msgstr "" 40 | 41 | #: spyder_unittest/unittestplugin.py:73 spyder_unittest/widgets/unittestgui.py:138 42 | msgid "Unit testing" 43 | msgstr "" 44 | 45 | #: spyder_unittest/unittestplugin.py:84 46 | msgid "Run test suites and view their results" 47 | msgstr "" 48 | 49 | #: spyder_unittest/unittestplugin.py:105 spyder_unittest/unittestplugin.py:106 spyder_unittest/widgets/unittestgui.py:424 50 | msgid "Run unit tests" 51 | msgstr "" 52 | 53 | #: spyder_unittest/widgets/configdialog.py:78 54 | msgid "Configure tests" 55 | msgstr "" 56 | 57 | #: spyder_unittest/widgets/configdialog.py:86 58 | msgid "Test framework:" 59 | msgstr "" 60 | 61 | #: spyder_unittest/widgets/configdialog.py:95 spyder_unittest/widgets/unittestgui.py:294 62 | msgid "not available" 63 | msgstr "" 64 | 65 | #: spyder_unittest/widgets/configdialog.py:102 66 | msgid "Command-line arguments:" 67 | msgstr "" 68 | 69 | #: spyder_unittest/widgets/configdialog.py:106 70 | msgid "Extra command-line arguments when running tests" 71 | msgstr "" 72 | 73 | #: spyder_unittest/widgets/configdialog.py:118 74 | msgid "Include coverage report in output" 75 | msgstr "" 76 | 77 | #: spyder_unittest/widgets/configdialog.py:119 78 | msgid "Works only for pytest, requires pytest-cov" 79 | msgstr "" 80 | 81 | #: spyder_unittest/widgets/configdialog.py:131 82 | msgid "Directory from which to run tests:" 83 | msgstr "" 84 | 85 | #: spyder_unittest/widgets/configdialog.py:137 spyder_unittest/widgets/configdialog.py:196 86 | msgid "Select directory" 87 | msgstr "" 88 | 89 | #: spyder_unittest/widgets/confpage.py:26 90 | msgid "Settings" 91 | msgstr "" 92 | 93 | #: spyder_unittest/widgets/confpage.py:28 94 | msgid "Abbreviate test names" 95 | msgstr "" 96 | 97 | #: spyder_unittest/widgets/datatree.py:45 98 | msgid "Message" 99 | msgstr "" 100 | 101 | #: spyder_unittest/widgets/datatree.py:45 102 | msgid "Name" 103 | msgstr "" 104 | 105 | #: spyder_unittest/widgets/datatree.py:45 106 | msgid "Status" 107 | msgstr "" 108 | 109 | #: spyder_unittest/widgets/datatree.py:45 110 | msgid "Time (ms)" 111 | msgstr "" 112 | 113 | #: spyder_unittest/widgets/datatree.py:153 114 | msgid "Collapse" 115 | msgstr "" 116 | 117 | #: spyder_unittest/widgets/datatree.py:156 118 | msgid "Expand" 119 | msgstr "" 120 | 121 | #: spyder_unittest/widgets/datatree.py:162 122 | msgid "Go to definition" 123 | msgstr "" 124 | 125 | #: spyder_unittest/widgets/datatree.py:169 126 | msgid "Run only this test" 127 | msgstr "" 128 | 129 | #: spyder_unittest/widgets/datatree.py:421 130 | msgid "test" 131 | msgstr "" 132 | 133 | #: spyder_unittest/widgets/datatree.py:421 134 | msgid "tests" 135 | msgstr "" 136 | 137 | #: spyder_unittest/widgets/datatree.py:425 138 | msgid "No results to show." 139 | msgstr "" 140 | 141 | #: spyder_unittest/widgets/datatree.py:430 142 | msgid "collected {}" 143 | msgstr "" 144 | 145 | #: spyder_unittest/widgets/datatree.py:431 146 | msgid "{} failed" 147 | msgstr "" 148 | 149 | #: spyder_unittest/widgets/datatree.py:432 150 | msgid ", {} passed" 151 | msgstr "" 152 | 153 | #: spyder_unittest/widgets/datatree.py:434 154 | msgid ", {} other" 155 | msgstr "" 156 | 157 | #: spyder_unittest/widgets/datatree.py:436 158 | msgid ", {} pending" 159 | msgstr "" 160 | 161 | #: spyder_unittest/widgets/datatree.py:441 162 | msgid ", {} coverage" 163 | msgstr "" 164 | 165 | #: spyder_unittest/widgets/unittestgui.py:151 166 | msgid "Configure ..." 167 | msgstr "" 168 | 169 | #: spyder_unittest/widgets/unittestgui.py:158 170 | msgid "Show output" 171 | msgstr "" 172 | 173 | #: spyder_unittest/widgets/unittestgui.py:165 174 | msgid "Collapse all" 175 | msgstr "" 176 | 177 | #: spyder_unittest/widgets/unittestgui.py:172 178 | msgid "Expand all" 179 | msgstr "" 180 | 181 | #: spyder_unittest/widgets/unittestgui.py:179 spyder_unittest/widgets/unittestgui.py:300 182 | msgid "Dependencies" 183 | msgstr "" 184 | 185 | #: spyder_unittest/widgets/unittestgui.py:248 186 | msgid "Unit testing output" 187 | msgstr "" 188 | 189 | #: spyder_unittest/widgets/unittestgui.py:291 190 | msgid "Versions of frameworks and their installed plugins:" 191 | msgstr "" 192 | 193 | #: spyder_unittest/widgets/unittestgui.py:393 194 | msgid "Error" 195 | msgstr "" 196 | 197 | #: spyder_unittest/widgets/unittestgui.py:393 198 | msgid "Process failed to start" 199 | msgstr "" 200 | 201 | #: spyder_unittest/widgets/unittestgui.py:396 202 | msgid "Running tests ..." 203 | msgstr "" 204 | 205 | #: spyder_unittest/widgets/unittestgui.py:417 206 | msgid "Stop" 207 | msgstr "" 208 | 209 | #: spyder_unittest/widgets/unittestgui.py:418 210 | msgid "Stop current test process" 211 | msgstr "" 212 | 213 | #: spyder_unittest/widgets/unittestgui.py:423 214 | msgid "Run tests" 215 | msgstr "" 216 | 217 | #: spyder_unittest/widgets/unittestgui.py:451 218 | msgid "Test process exited abnormally" 219 | msgstr "" 220 | 221 | #: spyder_unittest/widgets/unittestgui.py:460 222 | msgid "not run" 223 | msgstr "" 224 | 225 | #: spyder_unittest/widgets/unittestgui.py:467 spyder_unittest/widgets/unittestgui.py:473 226 | msgid "pending" 227 | msgstr "" 228 | 229 | #: spyder_unittest/widgets/unittestgui.py:474 230 | msgid "running" 231 | msgstr "" 232 | 233 | #: spyder_unittest/widgets/unittestgui.py:480 234 | msgid "failure" 235 | msgstr "" 236 | 237 | #: spyder_unittest/widgets/unittestgui.py:481 238 | msgid "collection error" 239 | msgstr "" 240 | 241 | -------------------------------------------------------------------------------- /spyder_unittest/locale/zh_CN/LC_MESSAGES/spyder_unittest.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: spyder-unittest\n" 4 | "POT-Creation-Date: 2023-06-23 16:41+0100\n" 5 | "PO-Revision-Date: 2023-06-23 20:17\n" 6 | "Last-Translator: \n" 7 | "Language-Team: Chinese Simplified\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Generated-By: pygettext.py 1.5\n" 12 | "Plural-Forms: nplurals=1; plural=0;\n" 13 | "X-Crowdin-Project: spyder-unittest\n" 14 | "X-Crowdin-Project-ID: 381839\n" 15 | "X-Crowdin-Language: zh-CN\n" 16 | "X-Crowdin-File: /master/spyder_unittest/locale/spyder_unittest.pot\n" 17 | "X-Crowdin-File-ID: 49\n" 18 | "Language: zh_CN\n" 19 | 20 | #: spyder_unittest/backend/nose2runner.py:99 21 | msgid "Captured stdout" 22 | msgstr "" 23 | 24 | #: spyder_unittest/backend/nose2runner.py:101 25 | msgid "Captured stderr" 26 | msgstr "" 27 | 28 | #: spyder_unittest/backend/pytestrunner.py:129 29 | msgid "(none)" 30 | msgstr "" 31 | 32 | #: spyder_unittest/backend/pytestrunner.py:129 33 | msgid "Missing: {}" 34 | msgstr "" 35 | 36 | #: spyder_unittest/backend/pytestrunner.py:130 37 | msgid "{}\n" 38 | "{}" 39 | msgstr "" 40 | 41 | #: spyder_unittest/unittestplugin.py:73 spyder_unittest/widgets/unittestgui.py:138 42 | msgid "Unit testing" 43 | msgstr "" 44 | 45 | #: spyder_unittest/unittestplugin.py:84 46 | msgid "Run test suites and view their results" 47 | msgstr "" 48 | 49 | #: spyder_unittest/unittestplugin.py:105 spyder_unittest/unittestplugin.py:106 spyder_unittest/widgets/unittestgui.py:424 50 | msgid "Run unit tests" 51 | msgstr "" 52 | 53 | #: spyder_unittest/widgets/configdialog.py:78 54 | msgid "Configure tests" 55 | msgstr "" 56 | 57 | #: spyder_unittest/widgets/configdialog.py:86 58 | msgid "Test framework:" 59 | msgstr "" 60 | 61 | #: spyder_unittest/widgets/configdialog.py:95 spyder_unittest/widgets/unittestgui.py:294 62 | msgid "not available" 63 | msgstr "" 64 | 65 | #: spyder_unittest/widgets/configdialog.py:102 66 | msgid "Command-line arguments:" 67 | msgstr "" 68 | 69 | #: spyder_unittest/widgets/configdialog.py:106 70 | msgid "Extra command-line arguments when running tests" 71 | msgstr "" 72 | 73 | #: spyder_unittest/widgets/configdialog.py:118 74 | msgid "Include coverage report in output" 75 | msgstr "" 76 | 77 | #: spyder_unittest/widgets/configdialog.py:119 78 | msgid "Works only for pytest, requires pytest-cov" 79 | msgstr "" 80 | 81 | #: spyder_unittest/widgets/configdialog.py:131 82 | msgid "Directory from which to run tests:" 83 | msgstr "" 84 | 85 | #: spyder_unittest/widgets/configdialog.py:137 spyder_unittest/widgets/configdialog.py:196 86 | msgid "Select directory" 87 | msgstr "" 88 | 89 | #: spyder_unittest/widgets/confpage.py:26 90 | msgid "Settings" 91 | msgstr "" 92 | 93 | #: spyder_unittest/widgets/confpage.py:28 94 | msgid "Abbreviate test names" 95 | msgstr "" 96 | 97 | #: spyder_unittest/widgets/datatree.py:45 98 | msgid "Message" 99 | msgstr "" 100 | 101 | #: spyder_unittest/widgets/datatree.py:45 102 | msgid "Name" 103 | msgstr "" 104 | 105 | #: spyder_unittest/widgets/datatree.py:45 106 | msgid "Status" 107 | msgstr "" 108 | 109 | #: spyder_unittest/widgets/datatree.py:45 110 | msgid "Time (ms)" 111 | msgstr "" 112 | 113 | #: spyder_unittest/widgets/datatree.py:153 114 | msgid "Collapse" 115 | msgstr "" 116 | 117 | #: spyder_unittest/widgets/datatree.py:156 118 | msgid "Expand" 119 | msgstr "" 120 | 121 | #: spyder_unittest/widgets/datatree.py:162 122 | msgid "Go to definition" 123 | msgstr "" 124 | 125 | #: spyder_unittest/widgets/datatree.py:169 126 | msgid "Run only this test" 127 | msgstr "" 128 | 129 | #: spyder_unittest/widgets/datatree.py:421 130 | msgid "test" 131 | msgstr "" 132 | 133 | #: spyder_unittest/widgets/datatree.py:421 134 | msgid "tests" 135 | msgstr "" 136 | 137 | #: spyder_unittest/widgets/datatree.py:425 138 | msgid "No results to show." 139 | msgstr "" 140 | 141 | #: spyder_unittest/widgets/datatree.py:430 142 | msgid "collected {}" 143 | msgstr "" 144 | 145 | #: spyder_unittest/widgets/datatree.py:431 146 | msgid "{} failed" 147 | msgstr "" 148 | 149 | #: spyder_unittest/widgets/datatree.py:432 150 | msgid ", {} passed" 151 | msgstr "" 152 | 153 | #: spyder_unittest/widgets/datatree.py:434 154 | msgid ", {} other" 155 | msgstr "" 156 | 157 | #: spyder_unittest/widgets/datatree.py:436 158 | msgid ", {} pending" 159 | msgstr "" 160 | 161 | #: spyder_unittest/widgets/datatree.py:441 162 | msgid ", {} coverage" 163 | msgstr "" 164 | 165 | #: spyder_unittest/widgets/unittestgui.py:151 166 | msgid "Configure ..." 167 | msgstr "" 168 | 169 | #: spyder_unittest/widgets/unittestgui.py:158 170 | msgid "Show output" 171 | msgstr "" 172 | 173 | #: spyder_unittest/widgets/unittestgui.py:165 174 | msgid "Collapse all" 175 | msgstr "" 176 | 177 | #: spyder_unittest/widgets/unittestgui.py:172 178 | msgid "Expand all" 179 | msgstr "" 180 | 181 | #: spyder_unittest/widgets/unittestgui.py:179 spyder_unittest/widgets/unittestgui.py:300 182 | msgid "Dependencies" 183 | msgstr "" 184 | 185 | #: spyder_unittest/widgets/unittestgui.py:248 186 | msgid "Unit testing output" 187 | msgstr "" 188 | 189 | #: spyder_unittest/widgets/unittestgui.py:291 190 | msgid "Versions of frameworks and their installed plugins:" 191 | msgstr "" 192 | 193 | #: spyder_unittest/widgets/unittestgui.py:393 194 | msgid "Error" 195 | msgstr "" 196 | 197 | #: spyder_unittest/widgets/unittestgui.py:393 198 | msgid "Process failed to start" 199 | msgstr "" 200 | 201 | #: spyder_unittest/widgets/unittestgui.py:396 202 | msgid "Running tests ..." 203 | msgstr "" 204 | 205 | #: spyder_unittest/widgets/unittestgui.py:417 206 | msgid "Stop" 207 | msgstr "" 208 | 209 | #: spyder_unittest/widgets/unittestgui.py:418 210 | msgid "Stop current test process" 211 | msgstr "" 212 | 213 | #: spyder_unittest/widgets/unittestgui.py:423 214 | msgid "Run tests" 215 | msgstr "" 216 | 217 | #: spyder_unittest/widgets/unittestgui.py:451 218 | msgid "Test process exited abnormally" 219 | msgstr "" 220 | 221 | #: spyder_unittest/widgets/unittestgui.py:460 222 | msgid "not run" 223 | msgstr "" 224 | 225 | #: spyder_unittest/widgets/unittestgui.py:467 spyder_unittest/widgets/unittestgui.py:473 226 | msgid "pending" 227 | msgstr "" 228 | 229 | #: spyder_unittest/widgets/unittestgui.py:474 230 | msgid "running" 231 | msgstr "" 232 | 233 | #: spyder_unittest/widgets/unittestgui.py:480 234 | msgid "failure" 235 | msgstr "" 236 | 237 | #: spyder_unittest/widgets/unittestgui.py:481 238 | msgid "collection error" 239 | msgstr "" 240 | 241 | -------------------------------------------------------------------------------- /spyder_unittest/locale/hr/LC_MESSAGES/spyder_unittest.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: spyder-unittest\n" 4 | "POT-Creation-Date: 2023-06-23 16:41+0100\n" 5 | "PO-Revision-Date: 2023-06-23 20:18\n" 6 | "Last-Translator: \n" 7 | "Language-Team: Croatian\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Generated-By: pygettext.py 1.5\n" 12 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" 13 | "X-Crowdin-Project: spyder-unittest\n" 14 | "X-Crowdin-Project-ID: 381839\n" 15 | "X-Crowdin-Language: hr\n" 16 | "X-Crowdin-File: /master/spyder_unittest/locale/spyder_unittest.pot\n" 17 | "X-Crowdin-File-ID: 49\n" 18 | "Language: hr_HR\n" 19 | 20 | #: spyder_unittest/backend/nose2runner.py:99 21 | msgid "Captured stdout" 22 | msgstr "" 23 | 24 | #: spyder_unittest/backend/nose2runner.py:101 25 | msgid "Captured stderr" 26 | msgstr "" 27 | 28 | #: spyder_unittest/backend/pytestrunner.py:129 29 | msgid "(none)" 30 | msgstr "" 31 | 32 | #: spyder_unittest/backend/pytestrunner.py:129 33 | msgid "Missing: {}" 34 | msgstr "" 35 | 36 | #: spyder_unittest/backend/pytestrunner.py:130 37 | msgid "{}\n" 38 | "{}" 39 | msgstr "" 40 | 41 | #: spyder_unittest/unittestplugin.py:73 spyder_unittest/widgets/unittestgui.py:138 42 | msgid "Unit testing" 43 | msgstr "" 44 | 45 | #: spyder_unittest/unittestplugin.py:84 46 | msgid "Run test suites and view their results" 47 | msgstr "" 48 | 49 | #: spyder_unittest/unittestplugin.py:105 spyder_unittest/unittestplugin.py:106 spyder_unittest/widgets/unittestgui.py:424 50 | msgid "Run unit tests" 51 | msgstr "" 52 | 53 | #: spyder_unittest/widgets/configdialog.py:78 54 | msgid "Configure tests" 55 | msgstr "" 56 | 57 | #: spyder_unittest/widgets/configdialog.py:86 58 | msgid "Test framework:" 59 | msgstr "" 60 | 61 | #: spyder_unittest/widgets/configdialog.py:95 spyder_unittest/widgets/unittestgui.py:294 62 | msgid "not available" 63 | msgstr "" 64 | 65 | #: spyder_unittest/widgets/configdialog.py:102 66 | msgid "Command-line arguments:" 67 | msgstr "" 68 | 69 | #: spyder_unittest/widgets/configdialog.py:106 70 | msgid "Extra command-line arguments when running tests" 71 | msgstr "" 72 | 73 | #: spyder_unittest/widgets/configdialog.py:118 74 | msgid "Include coverage report in output" 75 | msgstr "" 76 | 77 | #: spyder_unittest/widgets/configdialog.py:119 78 | msgid "Works only for pytest, requires pytest-cov" 79 | msgstr "" 80 | 81 | #: spyder_unittest/widgets/configdialog.py:131 82 | msgid "Directory from which to run tests:" 83 | msgstr "" 84 | 85 | #: spyder_unittest/widgets/configdialog.py:137 spyder_unittest/widgets/configdialog.py:196 86 | msgid "Select directory" 87 | msgstr "" 88 | 89 | #: spyder_unittest/widgets/confpage.py:26 90 | msgid "Settings" 91 | msgstr "" 92 | 93 | #: spyder_unittest/widgets/confpage.py:28 94 | msgid "Abbreviate test names" 95 | msgstr "" 96 | 97 | #: spyder_unittest/widgets/datatree.py:45 98 | msgid "Message" 99 | msgstr "" 100 | 101 | #: spyder_unittest/widgets/datatree.py:45 102 | msgid "Name" 103 | msgstr "" 104 | 105 | #: spyder_unittest/widgets/datatree.py:45 106 | msgid "Status" 107 | msgstr "" 108 | 109 | #: spyder_unittest/widgets/datatree.py:45 110 | msgid "Time (ms)" 111 | msgstr "" 112 | 113 | #: spyder_unittest/widgets/datatree.py:153 114 | msgid "Collapse" 115 | msgstr "" 116 | 117 | #: spyder_unittest/widgets/datatree.py:156 118 | msgid "Expand" 119 | msgstr "" 120 | 121 | #: spyder_unittest/widgets/datatree.py:162 122 | msgid "Go to definition" 123 | msgstr "" 124 | 125 | #: spyder_unittest/widgets/datatree.py:169 126 | msgid "Run only this test" 127 | msgstr "" 128 | 129 | #: spyder_unittest/widgets/datatree.py:421 130 | msgid "test" 131 | msgstr "" 132 | 133 | #: spyder_unittest/widgets/datatree.py:421 134 | msgid "tests" 135 | msgstr "" 136 | 137 | #: spyder_unittest/widgets/datatree.py:425 138 | msgid "No results to show." 139 | msgstr "" 140 | 141 | #: spyder_unittest/widgets/datatree.py:430 142 | msgid "collected {}" 143 | msgstr "" 144 | 145 | #: spyder_unittest/widgets/datatree.py:431 146 | msgid "{} failed" 147 | msgstr "" 148 | 149 | #: spyder_unittest/widgets/datatree.py:432 150 | msgid ", {} passed" 151 | msgstr "" 152 | 153 | #: spyder_unittest/widgets/datatree.py:434 154 | msgid ", {} other" 155 | msgstr "" 156 | 157 | #: spyder_unittest/widgets/datatree.py:436 158 | msgid ", {} pending" 159 | msgstr "" 160 | 161 | #: spyder_unittest/widgets/datatree.py:441 162 | msgid ", {} coverage" 163 | msgstr "" 164 | 165 | #: spyder_unittest/widgets/unittestgui.py:151 166 | msgid "Configure ..." 167 | msgstr "" 168 | 169 | #: spyder_unittest/widgets/unittestgui.py:158 170 | msgid "Show output" 171 | msgstr "" 172 | 173 | #: spyder_unittest/widgets/unittestgui.py:165 174 | msgid "Collapse all" 175 | msgstr "" 176 | 177 | #: spyder_unittest/widgets/unittestgui.py:172 178 | msgid "Expand all" 179 | msgstr "" 180 | 181 | #: spyder_unittest/widgets/unittestgui.py:179 spyder_unittest/widgets/unittestgui.py:300 182 | msgid "Dependencies" 183 | msgstr "" 184 | 185 | #: spyder_unittest/widgets/unittestgui.py:248 186 | msgid "Unit testing output" 187 | msgstr "" 188 | 189 | #: spyder_unittest/widgets/unittestgui.py:291 190 | msgid "Versions of frameworks and their installed plugins:" 191 | msgstr "" 192 | 193 | #: spyder_unittest/widgets/unittestgui.py:393 194 | msgid "Error" 195 | msgstr "" 196 | 197 | #: spyder_unittest/widgets/unittestgui.py:393 198 | msgid "Process failed to start" 199 | msgstr "" 200 | 201 | #: spyder_unittest/widgets/unittestgui.py:396 202 | msgid "Running tests ..." 203 | msgstr "" 204 | 205 | #: spyder_unittest/widgets/unittestgui.py:417 206 | msgid "Stop" 207 | msgstr "" 208 | 209 | #: spyder_unittest/widgets/unittestgui.py:418 210 | msgid "Stop current test process" 211 | msgstr "" 212 | 213 | #: spyder_unittest/widgets/unittestgui.py:423 214 | msgid "Run tests" 215 | msgstr "" 216 | 217 | #: spyder_unittest/widgets/unittestgui.py:451 218 | msgid "Test process exited abnormally" 219 | msgstr "" 220 | 221 | #: spyder_unittest/widgets/unittestgui.py:460 222 | msgid "not run" 223 | msgstr "" 224 | 225 | #: spyder_unittest/widgets/unittestgui.py:467 spyder_unittest/widgets/unittestgui.py:473 226 | msgid "pending" 227 | msgstr "" 228 | 229 | #: spyder_unittest/widgets/unittestgui.py:474 230 | msgid "running" 231 | msgstr "" 232 | 233 | #: spyder_unittest/widgets/unittestgui.py:480 234 | msgid "failure" 235 | msgstr "" 236 | 237 | #: spyder_unittest/widgets/unittestgui.py:481 238 | msgid "collection error" 239 | msgstr "" 240 | 241 | -------------------------------------------------------------------------------- /spyder_unittest/locale/pl/LC_MESSAGES/spyder_unittest.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: spyder-unittest\n" 4 | "POT-Creation-Date: 2023-06-23 16:41+0100\n" 5 | "PO-Revision-Date: 2023-06-23 20:17\n" 6 | "Last-Translator: \n" 7 | "Language-Team: Polish\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Generated-By: pygettext.py 1.5\n" 12 | "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" 13 | "X-Crowdin-Project: spyder-unittest\n" 14 | "X-Crowdin-Project-ID: 381839\n" 15 | "X-Crowdin-Language: pl\n" 16 | "X-Crowdin-File: /master/spyder_unittest/locale/spyder_unittest.pot\n" 17 | "X-Crowdin-File-ID: 49\n" 18 | "Language: pl_PL\n" 19 | 20 | #: spyder_unittest/backend/nose2runner.py:99 21 | msgid "Captured stdout" 22 | msgstr "" 23 | 24 | #: spyder_unittest/backend/nose2runner.py:101 25 | msgid "Captured stderr" 26 | msgstr "" 27 | 28 | #: spyder_unittest/backend/pytestrunner.py:129 29 | msgid "(none)" 30 | msgstr "" 31 | 32 | #: spyder_unittest/backend/pytestrunner.py:129 33 | msgid "Missing: {}" 34 | msgstr "" 35 | 36 | #: spyder_unittest/backend/pytestrunner.py:130 37 | msgid "{}\n" 38 | "{}" 39 | msgstr "" 40 | 41 | #: spyder_unittest/unittestplugin.py:73 spyder_unittest/widgets/unittestgui.py:138 42 | msgid "Unit testing" 43 | msgstr "" 44 | 45 | #: spyder_unittest/unittestplugin.py:84 46 | msgid "Run test suites and view their results" 47 | msgstr "" 48 | 49 | #: spyder_unittest/unittestplugin.py:105 spyder_unittest/unittestplugin.py:106 spyder_unittest/widgets/unittestgui.py:424 50 | msgid "Run unit tests" 51 | msgstr "" 52 | 53 | #: spyder_unittest/widgets/configdialog.py:78 54 | msgid "Configure tests" 55 | msgstr "" 56 | 57 | #: spyder_unittest/widgets/configdialog.py:86 58 | msgid "Test framework:" 59 | msgstr "" 60 | 61 | #: spyder_unittest/widgets/configdialog.py:95 spyder_unittest/widgets/unittestgui.py:294 62 | msgid "not available" 63 | msgstr "" 64 | 65 | #: spyder_unittest/widgets/configdialog.py:102 66 | msgid "Command-line arguments:" 67 | msgstr "" 68 | 69 | #: spyder_unittest/widgets/configdialog.py:106 70 | msgid "Extra command-line arguments when running tests" 71 | msgstr "" 72 | 73 | #: spyder_unittest/widgets/configdialog.py:118 74 | msgid "Include coverage report in output" 75 | msgstr "" 76 | 77 | #: spyder_unittest/widgets/configdialog.py:119 78 | msgid "Works only for pytest, requires pytest-cov" 79 | msgstr "" 80 | 81 | #: spyder_unittest/widgets/configdialog.py:131 82 | msgid "Directory from which to run tests:" 83 | msgstr "" 84 | 85 | #: spyder_unittest/widgets/configdialog.py:137 spyder_unittest/widgets/configdialog.py:196 86 | msgid "Select directory" 87 | msgstr "" 88 | 89 | #: spyder_unittest/widgets/confpage.py:26 90 | msgid "Settings" 91 | msgstr "" 92 | 93 | #: spyder_unittest/widgets/confpage.py:28 94 | msgid "Abbreviate test names" 95 | msgstr "" 96 | 97 | #: spyder_unittest/widgets/datatree.py:45 98 | msgid "Message" 99 | msgstr "" 100 | 101 | #: spyder_unittest/widgets/datatree.py:45 102 | msgid "Name" 103 | msgstr "" 104 | 105 | #: spyder_unittest/widgets/datatree.py:45 106 | msgid "Status" 107 | msgstr "" 108 | 109 | #: spyder_unittest/widgets/datatree.py:45 110 | msgid "Time (ms)" 111 | msgstr "" 112 | 113 | #: spyder_unittest/widgets/datatree.py:153 114 | msgid "Collapse" 115 | msgstr "" 116 | 117 | #: spyder_unittest/widgets/datatree.py:156 118 | msgid "Expand" 119 | msgstr "" 120 | 121 | #: spyder_unittest/widgets/datatree.py:162 122 | msgid "Go to definition" 123 | msgstr "" 124 | 125 | #: spyder_unittest/widgets/datatree.py:169 126 | msgid "Run only this test" 127 | msgstr "" 128 | 129 | #: spyder_unittest/widgets/datatree.py:421 130 | msgid "test" 131 | msgstr "" 132 | 133 | #: spyder_unittest/widgets/datatree.py:421 134 | msgid "tests" 135 | msgstr "" 136 | 137 | #: spyder_unittest/widgets/datatree.py:425 138 | msgid "No results to show." 139 | msgstr "" 140 | 141 | #: spyder_unittest/widgets/datatree.py:430 142 | msgid "collected {}" 143 | msgstr "" 144 | 145 | #: spyder_unittest/widgets/datatree.py:431 146 | msgid "{} failed" 147 | msgstr "" 148 | 149 | #: spyder_unittest/widgets/datatree.py:432 150 | msgid ", {} passed" 151 | msgstr "" 152 | 153 | #: spyder_unittest/widgets/datatree.py:434 154 | msgid ", {} other" 155 | msgstr "" 156 | 157 | #: spyder_unittest/widgets/datatree.py:436 158 | msgid ", {} pending" 159 | msgstr "" 160 | 161 | #: spyder_unittest/widgets/datatree.py:441 162 | msgid ", {} coverage" 163 | msgstr "" 164 | 165 | #: spyder_unittest/widgets/unittestgui.py:151 166 | msgid "Configure ..." 167 | msgstr "" 168 | 169 | #: spyder_unittest/widgets/unittestgui.py:158 170 | msgid "Show output" 171 | msgstr "" 172 | 173 | #: spyder_unittest/widgets/unittestgui.py:165 174 | msgid "Collapse all" 175 | msgstr "" 176 | 177 | #: spyder_unittest/widgets/unittestgui.py:172 178 | msgid "Expand all" 179 | msgstr "" 180 | 181 | #: spyder_unittest/widgets/unittestgui.py:179 spyder_unittest/widgets/unittestgui.py:300 182 | msgid "Dependencies" 183 | msgstr "" 184 | 185 | #: spyder_unittest/widgets/unittestgui.py:248 186 | msgid "Unit testing output" 187 | msgstr "" 188 | 189 | #: spyder_unittest/widgets/unittestgui.py:291 190 | msgid "Versions of frameworks and their installed plugins:" 191 | msgstr "" 192 | 193 | #: spyder_unittest/widgets/unittestgui.py:393 194 | msgid "Error" 195 | msgstr "" 196 | 197 | #: spyder_unittest/widgets/unittestgui.py:393 198 | msgid "Process failed to start" 199 | msgstr "" 200 | 201 | #: spyder_unittest/widgets/unittestgui.py:396 202 | msgid "Running tests ..." 203 | msgstr "" 204 | 205 | #: spyder_unittest/widgets/unittestgui.py:417 206 | msgid "Stop" 207 | msgstr "" 208 | 209 | #: spyder_unittest/widgets/unittestgui.py:418 210 | msgid "Stop current test process" 211 | msgstr "" 212 | 213 | #: spyder_unittest/widgets/unittestgui.py:423 214 | msgid "Run tests" 215 | msgstr "" 216 | 217 | #: spyder_unittest/widgets/unittestgui.py:451 218 | msgid "Test process exited abnormally" 219 | msgstr "" 220 | 221 | #: spyder_unittest/widgets/unittestgui.py:460 222 | msgid "not run" 223 | msgstr "" 224 | 225 | #: spyder_unittest/widgets/unittestgui.py:467 spyder_unittest/widgets/unittestgui.py:473 226 | msgid "pending" 227 | msgstr "" 228 | 229 | #: spyder_unittest/widgets/unittestgui.py:474 230 | msgid "running" 231 | msgstr "" 232 | 233 | #: spyder_unittest/widgets/unittestgui.py:480 234 | msgid "failure" 235 | msgstr "" 236 | 237 | #: spyder_unittest/widgets/unittestgui.py:481 238 | msgid "collection error" 239 | msgstr "" 240 | 241 | -------------------------------------------------------------------------------- /spyder_unittest/locale/ru/LC_MESSAGES/spyder_unittest.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: spyder-unittest\n" 4 | "POT-Creation-Date: 2023-06-23 16:41+0100\n" 5 | "PO-Revision-Date: 2023-06-23 20:17\n" 6 | "Last-Translator: \n" 7 | "Language-Team: Russian\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Generated-By: pygettext.py 1.5\n" 12 | "Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" 13 | "X-Crowdin-Project: spyder-unittest\n" 14 | "X-Crowdin-Project-ID: 381839\n" 15 | "X-Crowdin-Language: ru\n" 16 | "X-Crowdin-File: /master/spyder_unittest/locale/spyder_unittest.pot\n" 17 | "X-Crowdin-File-ID: 49\n" 18 | "Language: ru_RU\n" 19 | 20 | #: spyder_unittest/backend/nose2runner.py:99 21 | msgid "Captured stdout" 22 | msgstr "" 23 | 24 | #: spyder_unittest/backend/nose2runner.py:101 25 | msgid "Captured stderr" 26 | msgstr "" 27 | 28 | #: spyder_unittest/backend/pytestrunner.py:129 29 | msgid "(none)" 30 | msgstr "" 31 | 32 | #: spyder_unittest/backend/pytestrunner.py:129 33 | msgid "Missing: {}" 34 | msgstr "" 35 | 36 | #: spyder_unittest/backend/pytestrunner.py:130 37 | msgid "{}\n" 38 | "{}" 39 | msgstr "" 40 | 41 | #: spyder_unittest/unittestplugin.py:73 spyder_unittest/widgets/unittestgui.py:138 42 | msgid "Unit testing" 43 | msgstr "" 44 | 45 | #: spyder_unittest/unittestplugin.py:84 46 | msgid "Run test suites and view their results" 47 | msgstr "" 48 | 49 | #: spyder_unittest/unittestplugin.py:105 spyder_unittest/unittestplugin.py:106 spyder_unittest/widgets/unittestgui.py:424 50 | msgid "Run unit tests" 51 | msgstr "" 52 | 53 | #: spyder_unittest/widgets/configdialog.py:78 54 | msgid "Configure tests" 55 | msgstr "" 56 | 57 | #: spyder_unittest/widgets/configdialog.py:86 58 | msgid "Test framework:" 59 | msgstr "" 60 | 61 | #: spyder_unittest/widgets/configdialog.py:95 spyder_unittest/widgets/unittestgui.py:294 62 | msgid "not available" 63 | msgstr "" 64 | 65 | #: spyder_unittest/widgets/configdialog.py:102 66 | msgid "Command-line arguments:" 67 | msgstr "" 68 | 69 | #: spyder_unittest/widgets/configdialog.py:106 70 | msgid "Extra command-line arguments when running tests" 71 | msgstr "" 72 | 73 | #: spyder_unittest/widgets/configdialog.py:118 74 | msgid "Include coverage report in output" 75 | msgstr "" 76 | 77 | #: spyder_unittest/widgets/configdialog.py:119 78 | msgid "Works only for pytest, requires pytest-cov" 79 | msgstr "" 80 | 81 | #: spyder_unittest/widgets/configdialog.py:131 82 | msgid "Directory from which to run tests:" 83 | msgstr "" 84 | 85 | #: spyder_unittest/widgets/configdialog.py:137 spyder_unittest/widgets/configdialog.py:196 86 | msgid "Select directory" 87 | msgstr "" 88 | 89 | #: spyder_unittest/widgets/confpage.py:26 90 | msgid "Settings" 91 | msgstr "" 92 | 93 | #: spyder_unittest/widgets/confpage.py:28 94 | msgid "Abbreviate test names" 95 | msgstr "" 96 | 97 | #: spyder_unittest/widgets/datatree.py:45 98 | msgid "Message" 99 | msgstr "" 100 | 101 | #: spyder_unittest/widgets/datatree.py:45 102 | msgid "Name" 103 | msgstr "" 104 | 105 | #: spyder_unittest/widgets/datatree.py:45 106 | msgid "Status" 107 | msgstr "" 108 | 109 | #: spyder_unittest/widgets/datatree.py:45 110 | msgid "Time (ms)" 111 | msgstr "" 112 | 113 | #: spyder_unittest/widgets/datatree.py:153 114 | msgid "Collapse" 115 | msgstr "" 116 | 117 | #: spyder_unittest/widgets/datatree.py:156 118 | msgid "Expand" 119 | msgstr "" 120 | 121 | #: spyder_unittest/widgets/datatree.py:162 122 | msgid "Go to definition" 123 | msgstr "" 124 | 125 | #: spyder_unittest/widgets/datatree.py:169 126 | msgid "Run only this test" 127 | msgstr "" 128 | 129 | #: spyder_unittest/widgets/datatree.py:421 130 | msgid "test" 131 | msgstr "" 132 | 133 | #: spyder_unittest/widgets/datatree.py:421 134 | msgid "tests" 135 | msgstr "" 136 | 137 | #: spyder_unittest/widgets/datatree.py:425 138 | msgid "No results to show." 139 | msgstr "" 140 | 141 | #: spyder_unittest/widgets/datatree.py:430 142 | msgid "collected {}" 143 | msgstr "" 144 | 145 | #: spyder_unittest/widgets/datatree.py:431 146 | msgid "{} failed" 147 | msgstr "" 148 | 149 | #: spyder_unittest/widgets/datatree.py:432 150 | msgid ", {} passed" 151 | msgstr "" 152 | 153 | #: spyder_unittest/widgets/datatree.py:434 154 | msgid ", {} other" 155 | msgstr "" 156 | 157 | #: spyder_unittest/widgets/datatree.py:436 158 | msgid ", {} pending" 159 | msgstr "" 160 | 161 | #: spyder_unittest/widgets/datatree.py:441 162 | msgid ", {} coverage" 163 | msgstr "" 164 | 165 | #: spyder_unittest/widgets/unittestgui.py:151 166 | msgid "Configure ..." 167 | msgstr "" 168 | 169 | #: spyder_unittest/widgets/unittestgui.py:158 170 | msgid "Show output" 171 | msgstr "" 172 | 173 | #: spyder_unittest/widgets/unittestgui.py:165 174 | msgid "Collapse all" 175 | msgstr "" 176 | 177 | #: spyder_unittest/widgets/unittestgui.py:172 178 | msgid "Expand all" 179 | msgstr "" 180 | 181 | #: spyder_unittest/widgets/unittestgui.py:179 spyder_unittest/widgets/unittestgui.py:300 182 | msgid "Dependencies" 183 | msgstr "" 184 | 185 | #: spyder_unittest/widgets/unittestgui.py:248 186 | msgid "Unit testing output" 187 | msgstr "" 188 | 189 | #: spyder_unittest/widgets/unittestgui.py:291 190 | msgid "Versions of frameworks and their installed plugins:" 191 | msgstr "" 192 | 193 | #: spyder_unittest/widgets/unittestgui.py:393 194 | msgid "Error" 195 | msgstr "" 196 | 197 | #: spyder_unittest/widgets/unittestgui.py:393 198 | msgid "Process failed to start" 199 | msgstr "" 200 | 201 | #: spyder_unittest/widgets/unittestgui.py:396 202 | msgid "Running tests ..." 203 | msgstr "" 204 | 205 | #: spyder_unittest/widgets/unittestgui.py:417 206 | msgid "Stop" 207 | msgstr "" 208 | 209 | #: spyder_unittest/widgets/unittestgui.py:418 210 | msgid "Stop current test process" 211 | msgstr "" 212 | 213 | #: spyder_unittest/widgets/unittestgui.py:423 214 | msgid "Run tests" 215 | msgstr "" 216 | 217 | #: spyder_unittest/widgets/unittestgui.py:451 218 | msgid "Test process exited abnormally" 219 | msgstr "" 220 | 221 | #: spyder_unittest/widgets/unittestgui.py:460 222 | msgid "not run" 223 | msgstr "" 224 | 225 | #: spyder_unittest/widgets/unittestgui.py:467 spyder_unittest/widgets/unittestgui.py:473 226 | msgid "pending" 227 | msgstr "" 228 | 229 | #: spyder_unittest/widgets/unittestgui.py:474 230 | msgid "running" 231 | msgstr "" 232 | 233 | #: spyder_unittest/widgets/unittestgui.py:480 234 | msgid "failure" 235 | msgstr "" 236 | 237 | #: spyder_unittest/widgets/unittestgui.py:481 238 | msgid "collection error" 239 | msgstr "" 240 | 241 | -------------------------------------------------------------------------------- /spyder_unittest/tests/test_unittestplugin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """ 7 | Tests for the integration of the plugin with Spyder. 8 | """ 9 | 10 | # Standard library imports 11 | from collections import OrderedDict 12 | import os 13 | import time 14 | 15 | # Third party imports 16 | from qtpy.QtCore import Qt 17 | from qtpy.QtWidgets import QApplication 18 | 19 | # Spyder imports 20 | from spyder import version_info as spyder_version_info 21 | from spyder.api.plugins import Plugins 22 | from spyder.config.base import running_in_ci 23 | from spyder.plugins.mainmenu.api import ApplicationMenus 24 | 25 | # Local imports 26 | from spyder_unittest.unittestplugin import UnitTestPlugin 27 | from spyder_unittest.widgets.configdialog import Config 28 | 29 | 30 | def sleep_if_running_in_ci(): 31 | """ 32 | Sleep if the tests are running in GitHub CI. 33 | 34 | This prevents the error "QThread: Destroyed while thread is still running" 35 | for some reason. 36 | """ 37 | if running_in_ci(): 38 | time.sleep(5) 39 | QApplication.processEvents() 40 | 41 | 42 | def test_menu_item(main_window): 43 | """ 44 | Test that plugin adds item 'Run unit tests' to Run menu. 45 | """ 46 | main_menu = main_window.get_plugin(Plugins.MainMenu) 47 | run_menu = main_menu.get_application_menu(ApplicationMenus.Run) 48 | actions = run_menu.get_actions() 49 | 50 | # Filter out seperators (indicated by action is None) and convert to text 51 | menu_items = [action.text() for action in actions if action] 52 | 53 | assert 'Run unit tests' in menu_items 54 | 55 | 56 | def test_pythonpath_change(main_window): 57 | """ 58 | Test that pythonpath changes in Spyder propagate to UnitTestWidget. 59 | """ 60 | ppm = main_window.get_plugin(Plugins.PythonpathManager) 61 | unittest_plugin = main_window.get_plugin(UnitTestPlugin.NAME) 62 | 63 | new_path = '/some/path' 64 | new_path_dict = OrderedDict([(new_path, True)]) 65 | ppm.get_container()._save_paths(user_paths=new_path_dict) 66 | 67 | assert unittest_plugin.get_widget().pythonpath == [new_path] 68 | 69 | 70 | def test_default_working_dir(main_window, tmpdir): 71 | """ 72 | Test that plugin's default working dir is current working directory. 73 | After creating a project, the plugin's default working dir should be the 74 | same as the project directory. When the project is closed again, the 75 | plugin's default working dir should revert back to the current working 76 | directory. 77 | """ 78 | projects = main_window.get_plugin(Plugins.Projects) 79 | unittest_plugin = main_window.get_plugin(UnitTestPlugin.NAME) 80 | project_dir = str(tmpdir) 81 | 82 | assert unittest_plugin.get_widget().default_wdir == os.getcwd() 83 | 84 | sleep_if_running_in_ci() 85 | projects.create_project(project_dir) 86 | assert unittest_plugin.get_widget().default_wdir == project_dir 87 | 88 | sleep_if_running_in_ci() 89 | projects.close_project() 90 | assert unittest_plugin.get_widget().default_wdir == os.getcwd() 91 | 92 | 93 | def test_plugin_config(main_window, tmpdir, qtbot): 94 | """ 95 | Test that plugin uses the project's config file if a project is open. 96 | """ 97 | projects = main_window.get_plugin(Plugins.Projects) 98 | unittest_plugin = main_window.get_plugin(UnitTestPlugin.NAME) 99 | unittest_widget = unittest_plugin.get_widget() 100 | project_dir = str(tmpdir) 101 | config_file_path = tmpdir.join('.spyproject', 'config', 'unittest.ini') 102 | 103 | # Test config file does not exist and config is empty 104 | assert not config_file_path.check() 105 | assert unittest_widget.config is None 106 | 107 | # Create new project 108 | sleep_if_running_in_ci() 109 | projects.create_project(project_dir) 110 | 111 | # Test config file does exist but config is empty 112 | assert config_file_path.check() 113 | assert 'framework = ' in config_file_path.read().splitlines() 114 | assert unittest_widget.config is None 115 | 116 | # Set config and test that this is recorded in config file 117 | config = Config(framework='unittest', wdir=str(tmpdir)) 118 | with qtbot.waitSignal(unittest_widget.sig_newconfig): 119 | unittest_widget.config = config 120 | assert 'framework = unittest' in config_file_path.read().splitlines() 121 | 122 | # Close project and test that config is empty 123 | sleep_if_running_in_ci() 124 | projects.close_project() 125 | assert unittest_widget.config is None 126 | 127 | # Re-open project and test that config is correctly read 128 | sleep_if_running_in_ci() 129 | projects.open_project(project_dir) 130 | assert unittest_widget.config == config 131 | 132 | # Close project before ending test, which removes the project dir 133 | sleep_if_running_in_ci() 134 | projects.close_project() 135 | 136 | 137 | def test_go_to_test_definition(main_window, tmpdir, qtbot): 138 | """ 139 | Test that double clicking on a test result opens the file with the test 140 | definition in the editor with the cursor on the test definition. 141 | """ 142 | unittest_plugin = main_window.get_plugin(UnitTestPlugin.NAME) 143 | unittest_widget = unittest_plugin.get_widget() 144 | model = unittest_widget.testdatamodel 145 | view = unittest_widget.testdataview 146 | 147 | # Write test file 148 | testdir_str = str(tmpdir) 149 | testfile_str = tmpdir.join('test_foo.py').strpath 150 | os.chdir(testdir_str) 151 | with open(testfile_str, 'w') as f: 152 | f.write("def test_ok(): assert 1+1 == 2\n" 153 | "def test_fail(): assert 1+1 == 3\n") 154 | 155 | # Run tests 156 | config = Config(wdir=testdir_str, framework='pytest', coverage=False) 157 | with qtbot.waitSignal( 158 | unittest_widget.sig_finished, timeout=10000, raising=True): 159 | unittest_widget.run_tests(config) 160 | 161 | # Check that row 1 corresponds to `test_fail` 162 | index = model.index(1, 1) 163 | point = view.visualRect(index).center() 164 | assert view.indexAt(point).data(Qt.DisplayRole).endswith('test_fail') 165 | 166 | # Double click on `test_fail` 167 | unittest_plugin.switch_to_plugin() 168 | with qtbot.waitSignal(view.sig_edit_goto): 169 | qtbot.mouseClick(view.viewport(), Qt.LeftButton, pos=point, delay=100) 170 | qtbot.mouseDClick(view.viewport(), Qt.LeftButton, pos=point) 171 | 172 | # Check that test file is opened in editor 173 | editor = main_window.get_plugin(Plugins.Editor) 174 | filename = editor.get_current_filename() 175 | assert filename == testfile_str 176 | 177 | # Check that cursor is on line defining `test_fail` 178 | cursor = editor.get_current_editor().textCursor() 179 | line = cursor.block().text() 180 | assert line.startswith('def test_fail') 181 | -------------------------------------------------------------------------------- /spyder_unittest/locale/pt_BR/LC_MESSAGES/spyder_unittest.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: spyder-unittest\n" 4 | "POT-Creation-Date: 2023-06-23 16:41+0100\n" 5 | "PO-Revision-Date: 2023-06-24 20:15\n" 6 | "Last-Translator: \n" 7 | "Language-Team: Portuguese, Brazilian\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Generated-By: pygettext.py 1.5\n" 12 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 13 | "X-Crowdin-Project: spyder-unittest\n" 14 | "X-Crowdin-Project-ID: 381839\n" 15 | "X-Crowdin-Language: pt-BR\n" 16 | "X-Crowdin-File: /master/spyder_unittest/locale/spyder_unittest.pot\n" 17 | "X-Crowdin-File-ID: 49\n" 18 | "Language: pt_BR\n" 19 | 20 | #: spyder_unittest/backend/nose2runner.py:99 21 | msgid "Captured stdout" 22 | msgstr "stdout capturado" 23 | 24 | #: spyder_unittest/backend/nose2runner.py:101 25 | msgid "Captured stderr" 26 | msgstr "stderr capturado" 27 | 28 | #: spyder_unittest/backend/pytestrunner.py:129 29 | msgid "(none)" 30 | msgstr "" 31 | 32 | #: spyder_unittest/backend/pytestrunner.py:129 33 | msgid "Missing: {}" 34 | msgstr "" 35 | 36 | #: spyder_unittest/backend/pytestrunner.py:130 37 | msgid "{}\n" 38 | "{}" 39 | msgstr "" 40 | 41 | #: spyder_unittest/unittestplugin.py:73 spyder_unittest/widgets/unittestgui.py:138 42 | msgid "Unit testing" 43 | msgstr "Teste unitário" 44 | 45 | #: spyder_unittest/unittestplugin.py:84 46 | msgid "Run test suites and view their results" 47 | msgstr "" 48 | 49 | #: spyder_unittest/unittestplugin.py:105 spyder_unittest/unittestplugin.py:106 spyder_unittest/widgets/unittestgui.py:424 50 | msgid "Run unit tests" 51 | msgstr "Executar testes unitários" 52 | 53 | #: spyder_unittest/widgets/configdialog.py:78 54 | msgid "Configure tests" 55 | msgstr "Configurar testes" 56 | 57 | #: spyder_unittest/widgets/configdialog.py:86 58 | msgid "Test framework:" 59 | msgstr "Testar framework:" 60 | 61 | #: spyder_unittest/widgets/configdialog.py:95 spyder_unittest/widgets/unittestgui.py:294 62 | msgid "not available" 63 | msgstr "indisponível" 64 | 65 | #: spyder_unittest/widgets/configdialog.py:102 66 | msgid "Command-line arguments:" 67 | msgstr "" 68 | 69 | #: spyder_unittest/widgets/configdialog.py:106 70 | msgid "Extra command-line arguments when running tests" 71 | msgstr "" 72 | 73 | #: spyder_unittest/widgets/configdialog.py:118 74 | msgid "Include coverage report in output" 75 | msgstr "" 76 | 77 | #: spyder_unittest/widgets/configdialog.py:119 78 | msgid "Works only for pytest, requires pytest-cov" 79 | msgstr "" 80 | 81 | #: spyder_unittest/widgets/configdialog.py:131 82 | msgid "Directory from which to run tests:" 83 | msgstr "Diretório para execução dos testes:" 84 | 85 | #: spyder_unittest/widgets/configdialog.py:137 spyder_unittest/widgets/configdialog.py:196 86 | msgid "Select directory" 87 | msgstr "Selecione o diretório" 88 | 89 | #: spyder_unittest/widgets/confpage.py:26 90 | msgid "Settings" 91 | msgstr "" 92 | 93 | #: spyder_unittest/widgets/confpage.py:28 94 | msgid "Abbreviate test names" 95 | msgstr "" 96 | 97 | #: spyder_unittest/widgets/datatree.py:45 98 | msgid "Message" 99 | msgstr "Mensagem" 100 | 101 | #: spyder_unittest/widgets/datatree.py:45 102 | msgid "Name" 103 | msgstr "Nome" 104 | 105 | #: spyder_unittest/widgets/datatree.py:45 106 | msgid "Status" 107 | msgstr "Status" 108 | 109 | #: spyder_unittest/widgets/datatree.py:45 110 | msgid "Time (ms)" 111 | msgstr "Tempo (ms)" 112 | 113 | #: spyder_unittest/widgets/datatree.py:153 114 | msgid "Collapse" 115 | msgstr "Recolher" 116 | 117 | #: spyder_unittest/widgets/datatree.py:156 118 | msgid "Expand" 119 | msgstr "Expandir" 120 | 121 | #: spyder_unittest/widgets/datatree.py:162 122 | msgid "Go to definition" 123 | msgstr "Ir para definição" 124 | 125 | #: spyder_unittest/widgets/datatree.py:169 126 | msgid "Run only this test" 127 | msgstr "" 128 | 129 | #: spyder_unittest/widgets/datatree.py:421 130 | msgid "test" 131 | msgstr "teste" 132 | 133 | #: spyder_unittest/widgets/datatree.py:421 134 | msgid "tests" 135 | msgstr "testes" 136 | 137 | #: spyder_unittest/widgets/datatree.py:425 138 | msgid "No results to show." 139 | msgstr "Sem resultados para exibir." 140 | 141 | #: spyder_unittest/widgets/datatree.py:430 142 | msgid "collected {}" 143 | msgstr "coletado {}" 144 | 145 | #: spyder_unittest/widgets/datatree.py:431 146 | msgid "{} failed" 147 | msgstr "{} falhou" 148 | 149 | #: spyder_unittest/widgets/datatree.py:432 150 | msgid ", {} passed" 151 | msgstr ", {} passou" 152 | 153 | #: spyder_unittest/widgets/datatree.py:434 154 | msgid ", {} other" 155 | msgstr ", {} outro" 156 | 157 | #: spyder_unittest/widgets/datatree.py:436 158 | msgid ", {} pending" 159 | msgstr ", {} pendente" 160 | 161 | #: spyder_unittest/widgets/datatree.py:441 162 | msgid ", {} coverage" 163 | msgstr "" 164 | 165 | #: spyder_unittest/widgets/unittestgui.py:151 166 | msgid "Configure ..." 167 | msgstr "Configurar ..." 168 | 169 | #: spyder_unittest/widgets/unittestgui.py:158 170 | msgid "Show output" 171 | msgstr "Mostrar saída" 172 | 173 | #: spyder_unittest/widgets/unittestgui.py:165 174 | msgid "Collapse all" 175 | msgstr "Recolher tudo" 176 | 177 | #: spyder_unittest/widgets/unittestgui.py:172 178 | msgid "Expand all" 179 | msgstr "Expandir tudo" 180 | 181 | #: spyder_unittest/widgets/unittestgui.py:179 spyder_unittest/widgets/unittestgui.py:300 182 | msgid "Dependencies" 183 | msgstr "" 184 | 185 | #: spyder_unittest/widgets/unittestgui.py:248 186 | msgid "Unit testing output" 187 | msgstr "Saída do teste unitário" 188 | 189 | #: spyder_unittest/widgets/unittestgui.py:291 190 | msgid "Versions of frameworks and their installed plugins:" 191 | msgstr "" 192 | 193 | #: spyder_unittest/widgets/unittestgui.py:393 194 | msgid "Error" 195 | msgstr "Erro" 196 | 197 | #: spyder_unittest/widgets/unittestgui.py:393 198 | msgid "Process failed to start" 199 | msgstr "O processo falhou ao iniciar" 200 | 201 | #: spyder_unittest/widgets/unittestgui.py:396 202 | msgid "Running tests ..." 203 | msgstr "Executando teste..." 204 | 205 | #: spyder_unittest/widgets/unittestgui.py:417 206 | msgid "Stop" 207 | msgstr "Parar" 208 | 209 | #: spyder_unittest/widgets/unittestgui.py:418 210 | msgid "Stop current test process" 211 | msgstr "Parar processo de teste atual" 212 | 213 | #: spyder_unittest/widgets/unittestgui.py:423 214 | msgid "Run tests" 215 | msgstr "Executar testes" 216 | 217 | #: spyder_unittest/widgets/unittestgui.py:451 218 | msgid "Test process exited abnormally" 219 | msgstr "" 220 | 221 | #: spyder_unittest/widgets/unittestgui.py:460 222 | msgid "not run" 223 | msgstr "não executar" 224 | 225 | #: spyder_unittest/widgets/unittestgui.py:467 spyder_unittest/widgets/unittestgui.py:473 226 | msgid "pending" 227 | msgstr "pendente" 228 | 229 | #: spyder_unittest/widgets/unittestgui.py:474 230 | msgid "running" 231 | msgstr "executando" 232 | 233 | #: spyder_unittest/widgets/unittestgui.py:480 234 | msgid "failure" 235 | msgstr "falha" 236 | 237 | #: spyder_unittest/widgets/unittestgui.py:481 238 | msgid "collection error" 239 | msgstr "erro na compilação" 240 | 241 | -------------------------------------------------------------------------------- /spyder_unittest/widgets/tests/test_confpage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ----------------------------------------------------------------------------- 3 | # Copyright (c) 2023- Spyder Project Contributors 4 | # 5 | # Licensed under the terms of the MIT License 6 | # (see LICENSE.txt for details) 7 | # ----------------------------------------------------------------------------- 8 | 9 | # Standard library imports 10 | import sys 11 | import types 12 | from unittest.mock import Mock, MagicMock 13 | 14 | # Third party imports 15 | from qtpy.QtWidgets import QWidget, QMainWindow 16 | import pytest 17 | 18 | # Spyder imports 19 | from spyder.api.plugins import Plugins 20 | from spyder.api.plugin_registration.registry import PLUGIN_REGISTRY 21 | from spyder.app.cli_options import get_options 22 | from spyder.config.manager import CONF 23 | 24 | # Local imports 25 | from spyder_unittest.unittestplugin import UnitTestPlugin 26 | 27 | 28 | # ----------------------------------------------------------------------------- 29 | # 30 | # Classes and fixtures copied from spyder/plugins/preferences/tests/conftest.py 31 | 32 | class MainWindowMock(QMainWindow): 33 | register_shortcut = Mock() 34 | 35 | def __init__(self, parent): 36 | # This import assumes that an QApplication is already running, 37 | # so we can not put it at the top of the file 38 | from spyder.plugins.preferences.plugin import Preferences 39 | 40 | super().__init__(parent) 41 | self.default_style = None 42 | self.widgetlist = [] 43 | self.thirdparty_plugins = [] 44 | self.shortcut_data = [] 45 | self.prefs_dialog_instance = None 46 | self._APPLICATION_TOOLBARS = MagicMock() 47 | 48 | self.console = Mock() 49 | 50 | # To provide command line options for plugins that need them 51 | sys_argv = [sys.argv[0]] # Avoid options passed to pytest 52 | self._cli_options = get_options(sys_argv)[0] 53 | 54 | PLUGIN_REGISTRY.reset() 55 | PLUGIN_REGISTRY.sig_plugin_ready.connect(self.register_plugin) 56 | PLUGIN_REGISTRY.register_plugin(self, Preferences) 57 | 58 | # Load shortcuts for tests 59 | for context, name, __ in CONF.iter_shortcuts(): 60 | self.shortcut_data.append((None, context, name, None, None)) 61 | 62 | for attr in ['mem_status', 'cpu_status']: 63 | mock_attr = Mock() 64 | setattr(mock_attr, 'toolTip', lambda: '') 65 | setattr(mock_attr, 'setToolTip', lambda x: '') 66 | setattr(mock_attr, 'prefs_dialog_instance', lambda: '') 67 | setattr(self, attr, mock_attr) 68 | 69 | def register_plugin(self, plugin_name, external=False): 70 | plugin = PLUGIN_REGISTRY.get_plugin(plugin_name) 71 | plugin._register(omit_conf=True) 72 | 73 | def get_plugin(self, plugin_name, error=True): 74 | if plugin_name in PLUGIN_REGISTRY: 75 | return PLUGIN_REGISTRY.get_plugin(plugin_name) 76 | 77 | 78 | class ConfigDialogTester(QWidget): 79 | def __init__(self, parent, main_class, 80 | general_config_plugins, plugins): 81 | # This import assumes that an QApplication is already running, 82 | # so we can not put it at the top of the file 83 | from spyder.plugins.preferences.plugin import Preferences 84 | 85 | super().__init__(parent) 86 | self._main = main_class(self) if main_class else None 87 | if self._main is None: 88 | self._main = MainWindowMock(self) 89 | 90 | def register_plugin(self, plugin_name, external=False): 91 | plugin = PLUGIN_REGISTRY.get_plugin(plugin_name) 92 | plugin._register() 93 | 94 | def get_plugin(self, plugin_name, error=True): 95 | if plugin_name in PLUGIN_REGISTRY: 96 | return PLUGIN_REGISTRY.get_plugin(plugin_name) 97 | return None 98 | 99 | # Commented out because it gives the error: 100 | # A plugin with section "unittest" already exists! 101 | # setattr(self._main, 'register_plugin', 102 | # types.MethodType(register_plugin, self._main)) 103 | setattr(self._main, 'get_plugin', 104 | types.MethodType(get_plugin, self._main)) 105 | 106 | PLUGIN_REGISTRY.reset() 107 | PLUGIN_REGISTRY.sig_plugin_ready.connect(self._main.register_plugin) 108 | print(f'ConfigDialogTester registering {Preferences=}') 109 | PLUGIN_REGISTRY.register_plugin(self._main, Preferences) 110 | 111 | if plugins: 112 | for Plugin in plugins: 113 | if hasattr(Plugin, 'CONF_WIDGET_CLASS'): 114 | for required in (Plugin.REQUIRES or []): 115 | if required not in PLUGIN_REGISTRY: 116 | PLUGIN_REGISTRY.plugin_registry[required] = MagicMock() 117 | 118 | PLUGIN_REGISTRY.register_plugin(self._main, Plugin) 119 | else: 120 | plugin = Plugin(self._main) 121 | preferences = self._main.get_plugin(Plugins.Preferences) 122 | preferences.register_plugin_preferences(plugin) 123 | 124 | 125 | @pytest.fixture 126 | def config_dialog(qtbot, request): 127 | # mocker.patch.object(ima, 'icon', lambda x, *_: QIcon()) 128 | # Above line commented out from source because it gave an error 129 | 130 | main_class, general_config_plugins, plugins = request.param 131 | 132 | main_ref = ConfigDialogTester( 133 | None, main_class, general_config_plugins, plugins) 134 | qtbot.addWidget(main_ref) 135 | 136 | preferences = main_ref._main.get_plugin(Plugins.Preferences) 137 | preferences.open_dialog() 138 | container = preferences.get_container() 139 | dlg = container.dialog 140 | 141 | yield dlg 142 | 143 | dlg.close() 144 | 145 | 146 | # ----------------------------------------------------------------------------- 147 | # 148 | # Test for the spyder-unittest plugin 149 | 150 | @pytest.mark.parametrize( 151 | 'config_dialog', 152 | [[MainWindowMock, [], [UnitTestPlugin]]], 153 | indirect=True) 154 | def test_unittestconfigpage(config_dialog): 155 | """Test that changing "Abbreviate test names" works as expected.""" 156 | # Get reference to Preferences dialog and widget page to interact with 157 | dlg = config_dialog 158 | widget = config_dialog.get_page() 159 | 160 | # Assert default value of option in True 161 | assert widget.get_option('abbrev_test_names') is False 162 | 163 | # Toggle checkbox and assert that option value is now False 164 | widget.abbrev_box.click() 165 | dlg.apply_btn.click() 166 | assert widget.get_option('abbrev_test_names') is True 167 | 168 | # Reset options to default and check that option value is True again 169 | # Note: it is necessary to specify the section in reset_to_defaults() 170 | CONF.reset_to_defaults(section='unittest', notification=False) 171 | assert widget.get_option('abbrev_test_names') is False 172 | -------------------------------------------------------------------------------- /spyder_unittest/locale/fr/LC_MESSAGES/spyder_unittest.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: spyder-unittest\n" 4 | "POT-Creation-Date: 2023-06-23 16:41+0100\n" 5 | "PO-Revision-Date: 2023-06-24 20:15\n" 6 | "Last-Translator: \n" 7 | "Language-Team: French\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Generated-By: pygettext.py 1.5\n" 12 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 13 | "X-Crowdin-Project: spyder-unittest\n" 14 | "X-Crowdin-Project-ID: 381839\n" 15 | "X-Crowdin-Language: fr\n" 16 | "X-Crowdin-File: /master/spyder_unittest/locale/spyder_unittest.pot\n" 17 | "X-Crowdin-File-ID: 49\n" 18 | "Language: fr_FR\n" 19 | 20 | #: spyder_unittest/backend/nose2runner.py:99 21 | msgid "Captured stdout" 22 | msgstr "Stdout récupéré" 23 | 24 | #: spyder_unittest/backend/nose2runner.py:101 25 | msgid "Captured stderr" 26 | msgstr "Stderr récupéré" 27 | 28 | #: spyder_unittest/backend/pytestrunner.py:129 29 | msgid "(none)" 30 | msgstr "" 31 | 32 | #: spyder_unittest/backend/pytestrunner.py:129 33 | msgid "Missing: {}" 34 | msgstr "" 35 | 36 | #: spyder_unittest/backend/pytestrunner.py:130 37 | msgid "{}\n" 38 | "{}" 39 | msgstr "" 40 | 41 | #: spyder_unittest/unittestplugin.py:73 spyder_unittest/widgets/unittestgui.py:138 42 | msgid "Unit testing" 43 | msgstr "Tests unitaires" 44 | 45 | #: spyder_unittest/unittestplugin.py:84 46 | msgid "Run test suites and view their results" 47 | msgstr "" 48 | 49 | #: spyder_unittest/unittestplugin.py:105 spyder_unittest/unittestplugin.py:106 spyder_unittest/widgets/unittestgui.py:424 50 | msgid "Run unit tests" 51 | msgstr "Lancement des tests unitaires" 52 | 53 | #: spyder_unittest/widgets/configdialog.py:78 54 | msgid "Configure tests" 55 | msgstr "Configurer les tests" 56 | 57 | #: spyder_unittest/widgets/configdialog.py:86 58 | msgid "Test framework:" 59 | msgstr "Système de test:" 60 | 61 | #: spyder_unittest/widgets/configdialog.py:95 spyder_unittest/widgets/unittestgui.py:294 62 | msgid "not available" 63 | msgstr "non disponible" 64 | 65 | #: spyder_unittest/widgets/configdialog.py:102 66 | msgid "Command-line arguments:" 67 | msgstr "" 68 | 69 | #: spyder_unittest/widgets/configdialog.py:106 70 | msgid "Extra command-line arguments when running tests" 71 | msgstr "" 72 | 73 | #: spyder_unittest/widgets/configdialog.py:118 74 | msgid "Include coverage report in output" 75 | msgstr "" 76 | 77 | #: spyder_unittest/widgets/configdialog.py:119 78 | msgid "Works only for pytest, requires pytest-cov" 79 | msgstr "" 80 | 81 | #: spyder_unittest/widgets/configdialog.py:131 82 | msgid "Directory from which to run tests:" 83 | msgstr "Répertoire depuis lequel exécuter les tests:" 84 | 85 | #: spyder_unittest/widgets/configdialog.py:137 spyder_unittest/widgets/configdialog.py:196 86 | msgid "Select directory" 87 | msgstr "Sélectionner un dossier" 88 | 89 | #: spyder_unittest/widgets/confpage.py:26 90 | msgid "Settings" 91 | msgstr "" 92 | 93 | #: spyder_unittest/widgets/confpage.py:28 94 | msgid "Abbreviate test names" 95 | msgstr "" 96 | 97 | #: spyder_unittest/widgets/datatree.py:45 98 | msgid "Message" 99 | msgstr "Message" 100 | 101 | #: spyder_unittest/widgets/datatree.py:45 102 | msgid "Name" 103 | msgstr "Nom" 104 | 105 | #: spyder_unittest/widgets/datatree.py:45 106 | msgid "Status" 107 | msgstr "État " 108 | 109 | #: spyder_unittest/widgets/datatree.py:45 110 | msgid "Time (ms)" 111 | msgstr "Durée (ms)" 112 | 113 | #: spyder_unittest/widgets/datatree.py:153 114 | msgid "Collapse" 115 | msgstr "Réduire" 116 | 117 | #: spyder_unittest/widgets/datatree.py:156 118 | msgid "Expand" 119 | msgstr "Déployer" 120 | 121 | #: spyder_unittest/widgets/datatree.py:162 122 | msgid "Go to definition" 123 | msgstr "Aller à la définition" 124 | 125 | #: spyder_unittest/widgets/datatree.py:169 126 | msgid "Run only this test" 127 | msgstr "" 128 | 129 | #: spyder_unittest/widgets/datatree.py:421 130 | msgid "test" 131 | msgstr "test" 132 | 133 | #: spyder_unittest/widgets/datatree.py:421 134 | msgid "tests" 135 | msgstr "tests" 136 | 137 | #: spyder_unittest/widgets/datatree.py:425 138 | msgid "No results to show." 139 | msgstr "Aucun résultat à afficher." 140 | 141 | #: spyder_unittest/widgets/datatree.py:430 142 | msgid "collected {}" 143 | msgstr "{} collectés" 144 | 145 | #: spyder_unittest/widgets/datatree.py:431 146 | msgid "{} failed" 147 | msgstr "{} échec" 148 | 149 | #: spyder_unittest/widgets/datatree.py:432 150 | msgid ", {} passed" 151 | msgstr ", {} réussi" 152 | 153 | #: spyder_unittest/widgets/datatree.py:434 154 | msgid ", {} other" 155 | msgstr ", {} autres" 156 | 157 | #: spyder_unittest/widgets/datatree.py:436 158 | msgid ", {} pending" 159 | msgstr ", {} en cours" 160 | 161 | #: spyder_unittest/widgets/datatree.py:441 162 | msgid ", {} coverage" 163 | msgstr "" 164 | 165 | #: spyder_unittest/widgets/unittestgui.py:151 166 | msgid "Configure ..." 167 | msgstr "Configurer ..." 168 | 169 | #: spyder_unittest/widgets/unittestgui.py:158 170 | msgid "Show output" 171 | msgstr "Afficher les résultats" 172 | 173 | #: spyder_unittest/widgets/unittestgui.py:165 174 | msgid "Collapse all" 175 | msgstr "Tout réduire" 176 | 177 | #: spyder_unittest/widgets/unittestgui.py:172 178 | msgid "Expand all" 179 | msgstr "Tout déployer" 180 | 181 | #: spyder_unittest/widgets/unittestgui.py:179 spyder_unittest/widgets/unittestgui.py:300 182 | msgid "Dependencies" 183 | msgstr "" 184 | 185 | #: spyder_unittest/widgets/unittestgui.py:248 186 | msgid "Unit testing output" 187 | msgstr "Résultats de tests unitaires" 188 | 189 | #: spyder_unittest/widgets/unittestgui.py:291 190 | msgid "Versions of frameworks and their installed plugins:" 191 | msgstr "" 192 | 193 | #: spyder_unittest/widgets/unittestgui.py:393 194 | msgid "Error" 195 | msgstr "Erreur" 196 | 197 | #: spyder_unittest/widgets/unittestgui.py:393 198 | msgid "Process failed to start" 199 | msgstr "Le processus n'a pas pu démarrer" 200 | 201 | #: spyder_unittest/widgets/unittestgui.py:396 202 | msgid "Running tests ..." 203 | msgstr "Exécution des tests ..." 204 | 205 | #: spyder_unittest/widgets/unittestgui.py:417 206 | msgid "Stop" 207 | msgstr "Arrêter" 208 | 209 | #: spyder_unittest/widgets/unittestgui.py:418 210 | msgid "Stop current test process" 211 | msgstr "Arrêter le processus de test en cours" 212 | 213 | #: spyder_unittest/widgets/unittestgui.py:423 214 | msgid "Run tests" 215 | msgstr "Lancer les tests" 216 | 217 | #: spyder_unittest/widgets/unittestgui.py:451 218 | msgid "Test process exited abnormally" 219 | msgstr "" 220 | 221 | #: spyder_unittest/widgets/unittestgui.py:460 222 | msgid "not run" 223 | msgstr "non lancé" 224 | 225 | #: spyder_unittest/widgets/unittestgui.py:467 spyder_unittest/widgets/unittestgui.py:473 226 | msgid "pending" 227 | msgstr "en attente" 228 | 229 | #: spyder_unittest/widgets/unittestgui.py:474 230 | msgid "running" 231 | msgstr "en cours" 232 | 233 | #: spyder_unittest/widgets/unittestgui.py:480 234 | msgid "failure" 235 | msgstr "échec" 236 | 237 | #: spyder_unittest/widgets/unittestgui.py:481 238 | msgid "collection error" 239 | msgstr "erreur de collecte" 240 | 241 | -------------------------------------------------------------------------------- /spyder_unittest/backend/tests/test_unittestrunner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2017 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Tests for unittestrunner.py""" 7 | 8 | # Standard library imports 9 | import os.path as osp 10 | import sys 11 | from unittest.mock import Mock 12 | 13 | # Local imports 14 | from spyder_unittest.backend.unittestrunner import UnittestRunner 15 | from spyder_unittest.backend.runnerbase import Category, TestResult 16 | from spyder_unittest.widgets.configdialog import Config 17 | 18 | 19 | def test_unittestrunner_create_argument_list(monkeypatch): 20 | """ 21 | Test that UnittestRunner.createArgumentList() returns the expected list. 22 | """ 23 | config = Config(args=['--extra-arg']) 24 | cov_path = None 25 | MockZMQStreamReader = Mock() 26 | monkeypatch.setattr( 27 | 'spyder_unittest.backend.unittestrunner.ZmqStreamReader', 28 | MockZMQStreamReader) 29 | mock_reader = MockZMQStreamReader() 30 | mock_reader.port = 42 31 | runner = UnittestRunner(None, 'resultfile') 32 | runner.reader = mock_reader 33 | monkeypatch.setattr( 34 | 'spyder_unittest.backend.unittestrunner.osp.dirname', 35 | lambda _: 'dir') 36 | 37 | result = runner.create_argument_list(config, cov_path, None) 38 | 39 | pyfile = osp.join('dir', 'workers', 'unittestworker.py') 40 | assert result == [pyfile, '42', '--extra-arg'] 41 | 42 | 43 | def test_unittestrunner_start(monkeypatch): 44 | """ 45 | Test that UnittestRunner.start() sets the .config and .reader members 46 | correctly, that it connects to the reader's sig_received, and that it 47 | called the base class method. 48 | """ 49 | MockZMQStreamReader = Mock() 50 | monkeypatch.setattr( 51 | 'spyder_unittest.backend.unittestrunner.ZmqStreamReader', 52 | MockZMQStreamReader) 53 | mock_reader = MockZMQStreamReader() 54 | mock_base_start = Mock() 55 | monkeypatch.setattr('spyder_unittest.backend.unittestrunner.RunnerBase.start', 56 | mock_base_start) 57 | runner = UnittestRunner(None, 'results') 58 | config = Config() 59 | cov_path = None 60 | 61 | runner.start(config, cov_path, sys.executable, ['pythondir'], None) 62 | 63 | assert runner.config is config 64 | assert runner.reader is mock_reader 65 | runner.reader.sig_received.connect.assert_called_once_with( 66 | runner.process_output) 67 | mock_base_start.assert_called_once_with( 68 | config, cov_path, sys.executable, ['pythondir'], None) 69 | 70 | 71 | def test_unittestrunner_process_output_with_collected(qtbot): 72 | """Test UnittestRunner.processOutput() with two `collected` events.""" 73 | runner = UnittestRunner(None) 74 | output = [{'event': 'collected', 'id': 'spam.ham'}, 75 | {'event': 'collected', 'id': 'eggs.bacon'}] 76 | 77 | with qtbot.waitSignal(runner.sig_collected) as blocker: 78 | runner.process_output(output) 79 | 80 | expected = ['spam.ham', 'eggs.bacon'] 81 | assert blocker.args == [expected] 82 | 83 | 84 | def test_unittestrunner_process_output_with_starttest(qtbot): 85 | """Test UnittestRunner.processOutput() with two `startTest` events.""" 86 | runner = UnittestRunner(None) 87 | output = [{'event': 'startTest', 'id': 'spam.ham'}, 88 | {'event': 'startTest', 'id': 'eggs.bacon'}] 89 | 90 | with qtbot.waitSignal(runner.sig_starttest) as blocker: 91 | runner.process_output(output) 92 | 93 | expected = ['spam.ham', 'eggs.bacon'] 94 | assert blocker.args == [expected] 95 | 96 | 97 | def test_unittestrunner_process_output_with_addsuccess(qtbot): 98 | """Test UnittestRunner.processOutput() with an `addSuccess` event.""" 99 | runner = UnittestRunner(None) 100 | output = [{'event': 'addSuccess', 'id': 'spam.ham'}] 101 | 102 | with qtbot.waitSignal(runner.sig_testresult) as blocker: 103 | runner.process_output(output) 104 | 105 | expected = [TestResult(Category.OK, 'success', 'spam.ham')] 106 | assert blocker.args == [expected] 107 | 108 | 109 | def test_unittestrunner_process_output_with_addfailure(qtbot): 110 | """Test UnittestRunner.processOutput() with an `addFailure` event.""" 111 | runner = UnittestRunner(None) 112 | output = [{'event': 'addFailure', 113 | 'id': 'spam.ham', 114 | 'reason': 'exception', 115 | 'err': 'traceback'}] 116 | 117 | with qtbot.waitSignal(runner.sig_testresult) as blocker: 118 | runner.process_output(output) 119 | 120 | expected = [TestResult(Category.FAIL, 'failure', 'spam.ham', 121 | message='exception', extra_text='traceback')] 122 | assert blocker.args == [expected] 123 | 124 | 125 | def test_unittestrunner_process_output_with_adderror(qtbot): 126 | """Test UnittestRunner.processOutput() with an `addError` event.""" 127 | runner = UnittestRunner(None) 128 | output = [{'event': 'addError', 129 | 'id': 'spam.ham', 130 | 'reason': 'exception', 131 | 'err': 'traceback'}] 132 | 133 | with qtbot.waitSignal(runner.sig_testresult) as blocker: 134 | runner.process_output(output) 135 | 136 | expected = [TestResult(Category.FAIL, 'error', 'spam.ham', 137 | message='exception', extra_text='traceback')] 138 | assert blocker.args == [expected] 139 | 140 | 141 | def test_unittestrunner_process_output_with_addskip(qtbot): 142 | """Test UnittestRunner.processOutput() with an `addSkip` event.""" 143 | runner = UnittestRunner(None) 144 | output = [{'event': 'addSkip', 145 | 'id': 'spam.ham', 146 | 'reason': 'skip reason'}] 147 | 148 | with qtbot.waitSignal(runner.sig_testresult) as blocker: 149 | runner.process_output(output) 150 | 151 | expected = [TestResult(Category.SKIP, 'skip', 'spam.ham', 152 | message='skip reason')] 153 | assert blocker.args == [expected] 154 | 155 | 156 | def test_unittestrunner_process_output_with_addexpectedfailure(qtbot): 157 | """Test UnittestRunner.processOutput() with an `addExpectedFailure` event.""" 158 | runner = UnittestRunner(None) 159 | output = [{'event': 'addExpectedFailure', 160 | 'id': 'spam.ham', 161 | 'reason': 'exception', 162 | 'err': 'traceback'}] 163 | 164 | with qtbot.waitSignal(runner.sig_testresult) as blocker: 165 | runner.process_output(output) 166 | 167 | expected = [TestResult(Category.OK, 'expectedFailure', 'spam.ham', 168 | message='exception', extra_text='traceback')] 169 | assert blocker.args == [expected] 170 | 171 | 172 | def test_unittestrunner_process_output_with_addunexpectedsuccess(qtbot): 173 | """Test UnittestRunner.processOutput() with an `addUnexpectedSuccess` event.""" 174 | runner = UnittestRunner(None) 175 | output = [{'event': 'addUnexpectedSuccess', 'id': 'spam.ham'}] 176 | 177 | with qtbot.waitSignal(runner.sig_testresult) as blocker: 178 | runner.process_output(output) 179 | 180 | expected = [TestResult(Category.FAIL, 'unexpectedSuccess', 'spam.ham')] 181 | assert blocker.args == [expected] 182 | -------------------------------------------------------------------------------- /spyder_unittest/locale/de/LC_MESSAGES/spyder_unittest.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: spyder-unittest\n" 4 | "POT-Creation-Date: 2023-06-23 16:41+0100\n" 5 | "PO-Revision-Date: 2023-06-25 20:37\n" 6 | "Last-Translator: \n" 7 | "Language-Team: German\n" 8 | "MIME-Version: 1.0\n" 9 | "Content-Type: text/plain; charset=UTF-8\n" 10 | "Content-Transfer-Encoding: 8bit\n" 11 | "Generated-By: pygettext.py 1.5\n" 12 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 13 | "X-Crowdin-Project: spyder-unittest\n" 14 | "X-Crowdin-Project-ID: 381839\n" 15 | "X-Crowdin-Language: de\n" 16 | "X-Crowdin-File: /master/spyder_unittest/locale/spyder_unittest.pot\n" 17 | "X-Crowdin-File-ID: 49\n" 18 | "Language: de_DE\n" 19 | 20 | #: spyder_unittest/backend/nose2runner.py:99 21 | msgid "Captured stdout" 22 | msgstr "Erfasstes stdout" 23 | 24 | #: spyder_unittest/backend/nose2runner.py:101 25 | msgid "Captured stderr" 26 | msgstr "Erfasstes stderr" 27 | 28 | #: spyder_unittest/backend/pytestrunner.py:129 29 | msgid "(none)" 30 | msgstr "(keine)" 31 | 32 | #: spyder_unittest/backend/pytestrunner.py:129 33 | msgid "Missing: {}" 34 | msgstr "Fehlt: {}" 35 | 36 | #: spyder_unittest/backend/pytestrunner.py:130 37 | msgid "{}\n" 38 | "{}" 39 | msgstr "{}\n" 40 | "{}" 41 | 42 | #: spyder_unittest/unittestplugin.py:73 spyder_unittest/widgets/unittestgui.py:138 43 | msgid "Unit testing" 44 | msgstr "Unit-Tests" 45 | 46 | #: spyder_unittest/unittestplugin.py:84 47 | msgid "Run test suites and view their results" 48 | msgstr "Testsuiten ausführen und ihre Ergebnisse ansehen" 49 | 50 | #: spyder_unittest/unittestplugin.py:105 spyder_unittest/unittestplugin.py:106 spyder_unittest/widgets/unittestgui.py:424 51 | msgid "Run unit tests" 52 | msgstr "Unit-Tests ausführen" 53 | 54 | #: spyder_unittest/widgets/configdialog.py:78 55 | msgid "Configure tests" 56 | msgstr "Tests konfigurieren" 57 | 58 | #: spyder_unittest/widgets/configdialog.py:86 59 | msgid "Test framework:" 60 | msgstr "Test-Framework:" 61 | 62 | #: spyder_unittest/widgets/configdialog.py:95 spyder_unittest/widgets/unittestgui.py:294 63 | msgid "not available" 64 | msgstr "nicht verfügbar" 65 | 66 | #: spyder_unittest/widgets/configdialog.py:102 67 | msgid "Command-line arguments:" 68 | msgstr "Befehlszeilenargumente:" 69 | 70 | #: spyder_unittest/widgets/configdialog.py:106 71 | msgid "Extra command-line arguments when running tests" 72 | msgstr "Zusätzliche Befehlszeilenargumente bei der Ausführung von Tests" 73 | 74 | #: spyder_unittest/widgets/configdialog.py:118 75 | msgid "Include coverage report in output" 76 | msgstr "Abdeckungsbericht in Ausgabe einbeziehen" 77 | 78 | #: spyder_unittest/widgets/configdialog.py:119 79 | msgid "Works only for pytest, requires pytest-cov" 80 | msgstr "Funktioniert nur für pytest, erfordert pytest-cov" 81 | 82 | #: spyder_unittest/widgets/configdialog.py:131 83 | msgid "Directory from which to run tests:" 84 | msgstr "Verzeichnis, aus dem Tests ausgeführt werden sollen:" 85 | 86 | #: spyder_unittest/widgets/configdialog.py:137 spyder_unittest/widgets/configdialog.py:196 87 | msgid "Select directory" 88 | msgstr "Verzeichnis auswählen" 89 | 90 | #: spyder_unittest/widgets/confpage.py:26 91 | msgid "Settings" 92 | msgstr "Einstellungen" 93 | 94 | #: spyder_unittest/widgets/confpage.py:28 95 | msgid "Abbreviate test names" 96 | msgstr "Testnamen abkürzen" 97 | 98 | #: spyder_unittest/widgets/datatree.py:45 99 | msgid "Message" 100 | msgstr "Nachricht" 101 | 102 | #: spyder_unittest/widgets/datatree.py:45 103 | msgid "Name" 104 | msgstr "Name" 105 | 106 | #: spyder_unittest/widgets/datatree.py:45 107 | msgid "Status" 108 | msgstr "Status" 109 | 110 | #: spyder_unittest/widgets/datatree.py:45 111 | msgid "Time (ms)" 112 | msgstr "Zeit (ms)" 113 | 114 | #: spyder_unittest/widgets/datatree.py:153 115 | msgid "Collapse" 116 | msgstr "Einklappen" 117 | 118 | #: spyder_unittest/widgets/datatree.py:156 119 | msgid "Expand" 120 | msgstr "Ausklappen" 121 | 122 | #: spyder_unittest/widgets/datatree.py:162 123 | msgid "Go to definition" 124 | msgstr "Zur Definition gehen" 125 | 126 | #: spyder_unittest/widgets/datatree.py:169 127 | msgid "Run only this test" 128 | msgstr "Nur diesen Test ausführen" 129 | 130 | #: spyder_unittest/widgets/datatree.py:421 131 | msgid "test" 132 | msgstr "Test" 133 | 134 | #: spyder_unittest/widgets/datatree.py:421 135 | msgid "tests" 136 | msgstr "Tests" 137 | 138 | #: spyder_unittest/widgets/datatree.py:425 139 | msgid "No results to show." 140 | msgstr "Keine anzuzeigenden Ergebnisse." 141 | 142 | #: spyder_unittest/widgets/datatree.py:430 143 | msgid "collected {}" 144 | msgstr "{} gesammelt" 145 | 146 | #: spyder_unittest/widgets/datatree.py:431 147 | msgid "{} failed" 148 | msgstr "{} fehlgeschlagen" 149 | 150 | #: spyder_unittest/widgets/datatree.py:432 151 | msgid ", {} passed" 152 | msgstr ", {} bestanden" 153 | 154 | #: spyder_unittest/widgets/datatree.py:434 155 | msgid ", {} other" 156 | msgstr ", {} andere" 157 | 158 | #: spyder_unittest/widgets/datatree.py:436 159 | msgid ", {} pending" 160 | msgstr ", {} ausstehend" 161 | 162 | #: spyder_unittest/widgets/datatree.py:441 163 | msgid ", {} coverage" 164 | msgstr ", {} Abdeckung" 165 | 166 | #: spyder_unittest/widgets/unittestgui.py:151 167 | msgid "Configure ..." 168 | msgstr "Konfigurieren ..." 169 | 170 | #: spyder_unittest/widgets/unittestgui.py:158 171 | msgid "Show output" 172 | msgstr "Ausgabe anzeigen" 173 | 174 | #: spyder_unittest/widgets/unittestgui.py:165 175 | msgid "Collapse all" 176 | msgstr "Alle einklappen" 177 | 178 | #: spyder_unittest/widgets/unittestgui.py:172 179 | msgid "Expand all" 180 | msgstr "Alle ausklappen" 181 | 182 | #: spyder_unittest/widgets/unittestgui.py:179 spyder_unittest/widgets/unittestgui.py:300 183 | msgid "Dependencies" 184 | msgstr "Abhängigkeiten" 185 | 186 | #: spyder_unittest/widgets/unittestgui.py:248 187 | msgid "Unit testing output" 188 | msgstr "Ausgabe der Unit-Tests" 189 | 190 | #: spyder_unittest/widgets/unittestgui.py:291 191 | msgid "Versions of frameworks and their installed plugins:" 192 | msgstr "Versionen von Frameworks und deren installierte Plugins:" 193 | 194 | #: spyder_unittest/widgets/unittestgui.py:393 195 | msgid "Error" 196 | msgstr "Fehler" 197 | 198 | #: spyder_unittest/widgets/unittestgui.py:393 199 | msgid "Process failed to start" 200 | msgstr "Prozess konnte nicht gestartet werden" 201 | 202 | #: spyder_unittest/widgets/unittestgui.py:396 203 | msgid "Running tests ..." 204 | msgstr "Tests laufen ..." 205 | 206 | #: spyder_unittest/widgets/unittestgui.py:417 207 | msgid "Stop" 208 | msgstr "Stopp" 209 | 210 | #: spyder_unittest/widgets/unittestgui.py:418 211 | msgid "Stop current test process" 212 | msgstr "Aktuellen Testprozess stoppen" 213 | 214 | #: spyder_unittest/widgets/unittestgui.py:423 215 | msgid "Run tests" 216 | msgstr "Tests ausführen" 217 | 218 | #: spyder_unittest/widgets/unittestgui.py:451 219 | msgid "Test process exited abnormally" 220 | msgstr "Testprozess wurde abnormal beendet" 221 | 222 | #: spyder_unittest/widgets/unittestgui.py:460 223 | msgid "not run" 224 | msgstr "nicht ausgeführt" 225 | 226 | #: spyder_unittest/widgets/unittestgui.py:467 spyder_unittest/widgets/unittestgui.py:473 227 | msgid "pending" 228 | msgstr "ausstehend" 229 | 230 | #: spyder_unittest/widgets/unittestgui.py:474 231 | msgid "running" 232 | msgstr "läuft" 233 | 234 | #: spyder_unittest/widgets/unittestgui.py:480 235 | msgid "failure" 236 | msgstr "Fehlschlag" 237 | 238 | #: spyder_unittest/widgets/unittestgui.py:481 239 | msgid "collection error" 240 | msgstr "Sammlungsfehler" 241 | 242 | -------------------------------------------------------------------------------- /spyder_unittest/backend/workers/tests/test_unittestworker.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2017 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Tests for pytestworker.py""" 7 | 8 | # Standard library imports 9 | import os 10 | import os.path as osp 11 | import sys 12 | import unittest 13 | from unittest.mock import call, create_autospec, Mock 14 | 15 | # Third-party imports 16 | import pytest 17 | 18 | # Local imports 19 | # Modules in spyder_unittest.backend.workers assume that their directory 20 | # is in `sys.path`, so add that directory to the path. 21 | old_path = sys.path 22 | sys.path.insert(0, osp.join(osp.dirname(__file__), osp.pardir)) 23 | from spyder_unittest.backend.workers.unittestworker import ( 24 | main, report_collected, SpyderTestResult) 25 | from spyder_unittest.backend.workers.zmqwriter import ZmqStreamWriter 26 | sys.path = old_path 27 | 28 | 29 | class MyTest(unittest.TestCase): 30 | """Simple test class.""" 31 | def first(): pass 32 | def second(): pass 33 | 34 | 35 | @pytest.fixture 36 | def testresult(): 37 | mock_writer = create_autospec(ZmqStreamWriter) 38 | my_testresult = SpyderTestResult( 39 | stream=Mock(), descriptions=True, verbosity=2) 40 | my_testresult.writer = mock_writer 41 | my_testresult._exc_info_to_string = lambda err, test: 'some exception info' 42 | return my_testresult 43 | 44 | 45 | def test_spydertestresult_starttest(testresult): 46 | """Test that SpyderTestResult.startTest() writes the correct info.""" 47 | test = MyTest(methodName='first') 48 | testresult.startTest(test) 49 | expected = {'event': 'startTest', 'id': test.id()} 50 | testresult.writer.write.assert_called_once_with(expected) 51 | 52 | 53 | def test_spydertestresult_addsuccess(testresult): 54 | """Test that SpyderTestResult.addSuccess() writes the correct info.""" 55 | test = MyTest(methodName='first') 56 | testresult.addSuccess(test) 57 | expected = {'event': 'addSuccess', 'id': test.id()} 58 | testresult.writer.write.assert_called_once_with(expected) 59 | 60 | 61 | def test_spydertestresult_addfailure(testresult): 62 | """Test that SpyderTestResult.addFailure() writes the correct info.""" 63 | test = MyTest(methodName='first') 64 | err = ('notused', AssertionError('xxx'), 'notused') 65 | testresult.addFailure(test, err) 66 | expected = {'event': 'addFailure', 67 | 'id': test.id(), 68 | 'reason': 'AssertionError: xxx', 69 | 'err': 'some exception info'} 70 | testresult.writer.write.assert_called_once_with(expected) 71 | 72 | 73 | def test_spydertestresult_adderror(testresult): 74 | """Test that SpyderTestResult.addError() writes the correct info.""" 75 | test = MyTest(methodName='first') 76 | err = ('notused', AssertionError('xxx'), 'notused') 77 | testresult.addError(test, err) 78 | expected = {'event': 'addError', 79 | 'id': test.id(), 80 | 'reason': 'AssertionError: xxx', 81 | 'err': 'some exception info'} 82 | testresult.writer.write.assert_called_once_with(expected) 83 | 84 | 85 | def test_spydertestresult_addskip(testresult): 86 | """Test that SpyderTestResult.addSkip() writes the correct info.""" 87 | test = MyTest(methodName='first') 88 | reason = 'my reason' 89 | testresult.addSkip(test, reason) 90 | expected = {'event': 'addSkip', 91 | 'id': test.id(), 92 | 'reason': reason} 93 | testresult.writer.write.assert_called_once_with(expected) 94 | 95 | 96 | def test_spydertestresult_addexpectedfailure(testresult): 97 | """Test that SpyderTestResult.addExpectedFailure() writes the correct info.""" 98 | test = MyTest(methodName='first') 99 | err = ('notused', AssertionError('xxx'), 'notused') 100 | testresult.addExpectedFailure(test, err) 101 | expected = {'event': 'addExpectedFailure', 102 | 'id': test.id(), 103 | 'reason': 'AssertionError: xxx', 104 | 'err': 'some exception info'} 105 | testresult.writer.write.assert_called_once_with(expected) 106 | 107 | 108 | def test_spydertestresult_addunexpectedsuccess(testresult): 109 | """Test that SpyderTestResult.addUnexpectedSuccess() writes the correct info.""" 110 | test = MyTest(methodName='first') 111 | testresult.addUnexpectedSuccess(test) 112 | expected = {'event': 'addUnexpectedSuccess', 'id': test.id()} 113 | testresult.writer.write.assert_called_once_with(expected) 114 | 115 | 116 | def test_unittestworker_report_collected(): 117 | """ 118 | Test that report_collected() with a test suite containing two tests 119 | writes two `collected` events to the ZMQ stream. 120 | """ 121 | mock_writer = create_autospec(ZmqStreamWriter) 122 | test1 = MyTest(methodName='first') 123 | test2 = MyTest(methodName='second') 124 | test_suite_inner = unittest.TestSuite([test1, test2]) 125 | test_suite = unittest.TestSuite([test_suite_inner]) 126 | 127 | report_collected(mock_writer, test_suite) 128 | 129 | expected = [call({'event': 'collected', 'id': test1.id()}), 130 | call({'event': 'collected', 'id': test2.id()})] 131 | assert mock_writer.write.mock_calls == expected 132 | 133 | 134 | @pytest.fixture(scope='module') 135 | def testfile_path(tmp_path_factory): 136 | tmp_path = tmp_path_factory.mktemp('unittestworker') 137 | res = tmp_path / 'test_unittestworker_foo.py' 138 | res.write_text('import unittest\n' 139 | 'class MyTest(unittest.TestCase):\n' 140 | ' def test_ok(self): self.assertEqual(1+1, 2)\n' 141 | ' def test_fail(self): self.assertEqual(1+1, 3)\n') 142 | return res 143 | 144 | 145 | @pytest.mark.parametrize('alltests', [True, False]) 146 | def test_unittestworker_main(monkeypatch, testfile_path, alltests): 147 | """ 148 | Test that the main function with some tests writes the expected 149 | output to the ZMQ stream. 150 | """ 151 | mock_writer = create_autospec(ZmqStreamWriter) 152 | MockZmqStreamWriter = Mock(return_value=mock_writer) 153 | monkeypatch.setattr( 154 | 'spyder_unittest.backend.workers.unittestworker.ZmqStreamWriter', 155 | MockZmqStreamWriter) 156 | 157 | os.chdir(testfile_path.parent) 158 | testfilename = testfile_path.stem # `stem` removes the .py suffix 159 | main_args = ['mockscriptname', '42'] 160 | if not alltests: 161 | main_args.append(f'{testfilename}.MyTest.test_fail') 162 | main(main_args) 163 | 164 | args = mock_writer.write.call_args_list 165 | messages = [arg[0][0] for arg in args] 166 | assert len(messages) == (6 if alltests else 3) 167 | 168 | assert messages[0]['event'] == 'collected' 169 | assert messages[0]['id'] == f'{testfilename}.MyTest.test_fail' 170 | 171 | if alltests: 172 | n = 2 173 | assert messages[1]['event'] == 'collected' 174 | assert messages[1]['id'] == f'{testfilename}.MyTest.test_ok' 175 | else: 176 | n = 1 177 | 178 | assert messages[n]['event'] == 'startTest' 179 | assert messages[n]['id'] == f'{testfilename}.MyTest.test_fail' 180 | 181 | assert messages[n+1]['event'] == 'addFailure' 182 | assert messages[n+1]['id'] == f'{testfilename}.MyTest.test_fail' 183 | assert 'AssertionError' in messages[n+1]['reason'] 184 | assert 'assertEqual(1+1, 3)' in messages[n+1]['err'] 185 | 186 | if alltests: 187 | assert messages[n+2]['event'] == 'startTest' 188 | assert messages[n+2]['id'] == f'{testfilename}.MyTest.test_ok' 189 | 190 | assert messages[n+3]['event'] == 'addSuccess' 191 | assert messages[n+3]['id'] == f'{testfilename}.MyTest.test_ok' 192 | -------------------------------------------------------------------------------- /spyder_unittest/backend/runnerbase.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Classes for running tests within various frameworks.""" 7 | 8 | from __future__ import annotations 9 | 10 | # Standard library imports 11 | from enum import IntEnum 12 | import logging 13 | import os 14 | import tempfile 15 | from typing import ClassVar, Optional, TYPE_CHECKING 16 | 17 | # Third party imports 18 | from qtpy.QtCore import ( 19 | QObject, QProcess, QProcessEnvironment, QTextCodec, Signal) 20 | 21 | # Local imports 22 | if TYPE_CHECKING: 23 | from spyder_unittest.widgets.configdialog import Config 24 | from spyder_unittest.widgets.unittestgui import UnitTestWidget 25 | 26 | 27 | # Logging 28 | logger = logging.getLogger(__name__) 29 | 30 | # if generating coverage report, use this name for the TestResult 31 | # it's here in case we can get coverage results from unittest too 32 | COV_TEST_NAME = 'Total Test Coverage' 33 | 34 | 35 | class Category(IntEnum): 36 | """Enum type representing category of test result.""" 37 | 38 | FAIL = 1 39 | OK = 2 40 | SKIP = 3 41 | PENDING = 4 42 | COVERAGE = 5 43 | 44 | 45 | class TestResult: 46 | """Class representing the result of running a single test.""" 47 | 48 | __test__ = False # this is not a pytest test class 49 | 50 | def __init__(self, category: Category, status: str, name: str, 51 | message: str = '', time: Optional[float] = None, 52 | extra_text: str = '', filename: Optional[str] = None, 53 | lineno: Optional[int] = None): 54 | """ 55 | Construct a test result. 56 | """ 57 | self.category = category 58 | self.status = status 59 | self.name = name 60 | self.message = message 61 | self.time = time 62 | extra_text = extra_text.rstrip() 63 | if extra_text: 64 | self.extra_text = extra_text.split("\n") 65 | else: 66 | self.extra_text = [] 67 | self.filename = filename 68 | self.lineno = lineno 69 | 70 | def __eq__(self, other: object) -> bool: 71 | """Test for equality.""" 72 | if not isinstance(other, TestResult): 73 | return NotImplemented 74 | return self.__dict__ == other.__dict__ 75 | 76 | 77 | class RunnerBase(QObject): 78 | """ 79 | Base class for running tests with a framework that uses JUnit XML. 80 | 81 | This is an abstract class, meant to be subclassed before being used. 82 | Concrete subclasses should define create_argument_list() and finished(). 83 | 84 | All communication back to the caller is done via signals. 85 | 86 | Attributes 87 | ---------- 88 | module : str 89 | Name of Python module for test framework. This needs to be defined 90 | before the user can run tests. 91 | name : str 92 | Name of test framework, as presented to user. 93 | process : QProcess or None 94 | Process running the unit test suite. 95 | resultfilename : str 96 | Name of file in which test results are stored. 97 | 98 | Signals 99 | ------- 100 | sig_collected(list of str) 101 | Emitted when tests are collected. 102 | sig_collecterror(list of (str, str) tuples) 103 | Emitted when errors are encountered during collection. First element 104 | of tuple is test name, second element is error message. 105 | sig_starttest(list of str) 106 | Emitted just before tests are run. 107 | sig_testresult(list of TestResult) 108 | Emitted when tests are finished. 109 | sig_finished(list of TestResult, str, bool) 110 | Emitted when test process finishes. First argument contains the test 111 | results, second argument contains the output of the test process, 112 | third argument is True on normal exit, False on abnormal exit. 113 | sig_stop() 114 | Emitted when test process is being stopped. 115 | """ 116 | 117 | module: ClassVar[str] 118 | name: ClassVar[str] 119 | 120 | sig_collected = Signal(object) 121 | sig_collecterror = Signal(object) 122 | sig_starttest = Signal(object) 123 | sig_testresult = Signal(object) 124 | sig_finished = Signal(object, str, bool) 125 | sig_stop = Signal() 126 | 127 | def __init__(self, widget: UnitTestWidget, 128 | resultfilename: Optional[str] = None): 129 | """ 130 | Construct test runner. 131 | 132 | Parameters 133 | ---------- 134 | widget : UnitTestWidget 135 | Unit test widget which constructs the test runner. 136 | resultfilename : str or None 137 | Name of file in which to store test results. If None, use default. 138 | """ 139 | QObject.__init__(self, widget) 140 | self.process: Optional[QProcess] = None 141 | if resultfilename is None: 142 | self.resultfilename = os.path.join(tempfile.gettempdir(), 143 | 'unittest.results') 144 | else: 145 | self.resultfilename = resultfilename 146 | 147 | def create_argument_list(self, config: Config, 148 | cov_path: Optional[str], 149 | single_test: Optional[str]) -> list[str]: 150 | """ 151 | Create argument list for testing process (dummy). 152 | 153 | This function should be defined before calling self.start(). 154 | """ 155 | raise NotImplementedError 156 | 157 | def _prepare_process(self, config: Config, 158 | pythonpath: list[str]) -> QProcess: 159 | """ 160 | Prepare and return process for running the unit test suite. 161 | 162 | This sets the working directory and environment. 163 | """ 164 | process = QProcess(self) 165 | process.setProcessChannelMode(QProcess.MergedChannels) 166 | process.setWorkingDirectory(config.wdir) 167 | process.finished.connect(self.finished) 168 | if pythonpath: 169 | env = QProcessEnvironment.systemEnvironment() 170 | old_python_path = env.value('PYTHONPATH', '') 171 | python_path_str = os.pathsep.join(pythonpath) 172 | if old_python_path: 173 | python_path_str += os.pathsep + old_python_path 174 | env.insert('PYTHONPATH', python_path_str) 175 | process.setProcessEnvironment(env) 176 | return process 177 | 178 | def start(self, config: Config, cov_path: Optional[str], 179 | executable: str, pythonpath: list[str], 180 | single_test: Optional[str]) -> None: 181 | """ 182 | Start process which will run the unit test suite. 183 | 184 | The process is run in the working directory specified in 'config', 185 | with the directories in `pythonpath` added to the Python path for the 186 | test process. The test results are written to the file 187 | `self.resultfilename`. The standard output and error are also recorded. 188 | Once the process is finished, `self.finished()` will be called. 189 | 190 | Parameters 191 | ---------- 192 | config 193 | Unit test configuration. 194 | cov_path 195 | Path to filter source for coverage report 196 | executable 197 | Path to Python executable 198 | pythonpath 199 | List of directories to be added to the Python path 200 | single_test 201 | If None, run all tests; otherwise, it is the name of the only test 202 | to be run. 203 | 204 | Raises 205 | ------ 206 | RuntimeError 207 | If process failed to start. 208 | """ 209 | self.process = self._prepare_process(config, pythonpath) 210 | p_args = self.create_argument_list(config, cov_path, single_test) 211 | p_args = ['-X', 'utf8'] + p_args # Ensure output is UTF-8 212 | try: 213 | os.remove(self.resultfilename) 214 | except OSError: 215 | pass 216 | logger.debug(f'Starting Python process with arguments {p_args}') 217 | self.process.start(executable, p_args) 218 | running = self.process.waitForStarted() 219 | if not running: 220 | raise RuntimeError 221 | 222 | def finished(self, exitcode: int) -> None: 223 | """ 224 | Called when the unit test process has finished. 225 | 226 | This function should be implemented in derived classes. It should read 227 | the results (if necessary) and emit `sig_finished`. 228 | """ 229 | raise NotImplementedError 230 | 231 | def read_all_process_output(self) -> str: 232 | """Read and return all output from `self.process` as unicode.""" 233 | assert self.process is not None 234 | qbytearray = self.process.readAllStandardOutput() 235 | return str(qbytearray.data(), encoding='utf-8') 236 | 237 | def stop_if_running(self) -> None: 238 | """Stop testing process if it is running.""" 239 | if self.process and self.process.state() == QProcess.Running: 240 | self.process.kill() 241 | self.sig_stop.emit() 242 | -------------------------------------------------------------------------------- /spyder_unittest/widgets/configdialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """ 7 | Functionality for asking the user to specify the test configuration. 8 | 9 | The main entry point is `ask_for_config()`. 10 | """ 11 | 12 | from __future__ import annotations 13 | 14 | # Standard library imports 15 | from os import getcwd 16 | import os.path as osp 17 | import shlex 18 | from typing import Optional, NamedTuple 19 | 20 | # Third party imports 21 | from qtpy.compat import getexistingdirectory 22 | from qtpy.QtCore import Slot 23 | from qtpy.QtWidgets import ( 24 | QApplication, QComboBox, QDialog, QDialogButtonBox, QGridLayout, 25 | QHBoxLayout, QLabel, QLineEdit, QPushButton, QVBoxLayout, QCheckBox) 26 | from spyder.config.base import get_translation 27 | from spyder.utils import icon_manager as ima 28 | 29 | 30 | try: 31 | _ = get_translation('spyder_unittest') 32 | except KeyError: 33 | import gettext 34 | _ = gettext.gettext 35 | 36 | class Config(NamedTuple): 37 | framework: Optional[str] = None 38 | wdir: str = '' 39 | coverage: bool = False 40 | args: list[str] = [] 41 | 42 | 43 | class ConfigDialog(QDialog): 44 | """ 45 | Dialog window for specifying test configuration. 46 | 47 | The window contains a combobox with all the frameworks, a line edit box for 48 | specifying the working directory, a button to use a file browser for 49 | selecting the directory, and OK and Cancel buttons. Initially, no framework 50 | is selected and the OK button is disabled. Selecting a framework enables 51 | the OK button. 52 | """ 53 | 54 | # Width of strut in the layout of the dialog window; this determines 55 | # the width of the dialog 56 | STRUT_WIDTH = 400 57 | 58 | # Extra vertical space added between elements in the dialog 59 | EXTRA_SPACE = 10 60 | 61 | def __init__(self, frameworks, config, versions, parent=None): 62 | """ 63 | Construct a dialog window. 64 | 65 | Parameters 66 | ---------- 67 | frameworks : dict of (str, type) 68 | Names of all supported frameworks with their associated class 69 | (assumed to be a subclass of RunnerBase) 70 | config : Config 71 | Initial configuration 72 | versions : dict 73 | Versions of testing frameworks and their plugins 74 | parent : QWidget 75 | """ 76 | super().__init__(parent) 77 | self.versions = versions 78 | self.setWindowTitle(_('Configure tests')) 79 | layout = QVBoxLayout(self) 80 | layout.addStrut(self.STRUT_WIDTH) 81 | 82 | grid_layout = QGridLayout() 83 | 84 | # Combo box for selecting the test framework 85 | 86 | framework_label = QLabel(_('Test framework:')) 87 | grid_layout.addWidget(framework_label, 0, 0) 88 | 89 | self.framework_combobox = QComboBox(self) 90 | for ix, (name, runner) in enumerate(sorted(frameworks.items())): 91 | installed = versions[name]['available'] 92 | if installed: 93 | label = name 94 | else: 95 | label = '{} ({})'.format(name, _('not available')) 96 | self.framework_combobox.addItem(label) 97 | self.framework_combobox.model().item(ix).setEnabled(installed) 98 | grid_layout.addWidget(self.framework_combobox, 0, 1) 99 | 100 | # Line edit field for adding extra command-line arguments 101 | 102 | args_label = QLabel(_('Command-line arguments:')) 103 | grid_layout.addWidget(args_label, 1, 0) 104 | 105 | self.args_lineedit = QLineEdit(self) 106 | args_toolTip = _('Extra command-line arguments when running tests') 107 | self.args_lineedit.setToolTip(args_toolTip) 108 | grid_layout.addWidget(self.args_lineedit, 1, 1) 109 | 110 | layout.addLayout(grid_layout) 111 | spacing = grid_layout.verticalSpacing() + self.EXTRA_SPACE 112 | grid_layout.setVerticalSpacing(spacing) 113 | 114 | layout.addSpacing(self.EXTRA_SPACE) 115 | 116 | # Checkbox for enabling coverage report 117 | 118 | coverage_label = _('Include coverage report in output') 119 | coverage_toolTip = _('Works only for pytest, requires pytest-cov') 120 | coverage_layout = QHBoxLayout() 121 | self.coverage_checkbox = QCheckBox(coverage_label, self) 122 | self.coverage_checkbox.setToolTip(coverage_toolTip) 123 | self.coverage_checkbox.setEnabled(False) 124 | coverage_layout.addWidget(self.coverage_checkbox) 125 | layout.addLayout(coverage_layout) 126 | 127 | layout.addSpacing(self.EXTRA_SPACE) 128 | 129 | # Line edit field for selecting directory 130 | 131 | wdir_label = QLabel(_('Directory from which to run tests:')) 132 | layout.addWidget(wdir_label) 133 | wdir_layout = QHBoxLayout() 134 | self.wdir_lineedit = QLineEdit(self) 135 | wdir_layout.addWidget(self.wdir_lineedit) 136 | self.wdir_button = QPushButton(ima.icon('DirOpenIcon'), '', self) 137 | self.wdir_button.setToolTip(_("Select directory")) 138 | self.wdir_button.clicked.connect(lambda: self.select_directory()) 139 | wdir_layout.addWidget(self.wdir_button) 140 | layout.addLayout(wdir_layout) 141 | 142 | layout.addSpacing(2 * self.EXTRA_SPACE) 143 | 144 | # OK and Cancel buttons at the bottom 145 | 146 | self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | 147 | QDialogButtonBox.Cancel) 148 | layout.addWidget(self.buttons) 149 | self.buttons.accepted.connect(self.accept) 150 | self.buttons.rejected.connect(self.reject) 151 | 152 | self.ok_button = self.buttons.button(QDialogButtonBox.Ok) 153 | self.ok_button.setEnabled(False) 154 | self.framework_combobox.currentIndexChanged.connect( 155 | self.framework_changed) 156 | 157 | # Set initial values to agree with the given config 158 | 159 | self.framework_combobox.setCurrentIndex(-1) 160 | if config.framework: 161 | index = self.framework_combobox.findText(config.framework) 162 | if index != -1: 163 | self.framework_combobox.setCurrentIndex(index) 164 | self.coverage_checkbox.setChecked(config.coverage) 165 | self.enable_coverage_checkbox_if_available() 166 | self.args_lineedit.setText(shlex.join(config.args)) 167 | self.wdir_lineedit.setText(config.wdir) 168 | 169 | @Slot(int) 170 | def framework_changed(self, index): 171 | """Called when selected framework changes.""" 172 | if index != -1: 173 | self.ok_button.setEnabled(True) 174 | self.enable_coverage_checkbox_if_available() 175 | 176 | def enable_coverage_checkbox_if_available(self): 177 | """ 178 | Enable coverage checkbox only if coverage is available. 179 | 180 | Coverage is only implemented for pytest and requires pytest_cov. 181 | Enable the coverage checkbox if these conditions are satisfied, 182 | otherwise, disable and un-check the checkbox. 183 | """ 184 | if (str(self.framework_combobox.currentText()) != 'pytest' 185 | or 'pytest-cov' not in self.versions['pytest']['plugins']): 186 | self.coverage_checkbox.setEnabled(False) 187 | self.coverage_checkbox.setChecked(False) 188 | else: 189 | self.coverage_checkbox.setEnabled(True) 190 | 191 | def select_directory(self): 192 | """Display dialog for user to select working directory.""" 193 | basedir = self.wdir_lineedit.text() 194 | if not osp.isdir(basedir): 195 | basedir = getcwd() 196 | title = _("Select directory") 197 | directory = getexistingdirectory(self, title, basedir) 198 | if directory: 199 | self.wdir_lineedit.setText(directory) 200 | 201 | def get_config(self): 202 | """ 203 | Return the test configuration specified by the user. 204 | 205 | Returns 206 | ------- 207 | Config 208 | Test configuration 209 | """ 210 | framework = self.framework_combobox.currentText() 211 | if framework == '': 212 | framework = None 213 | 214 | args = self.args_lineedit.text() 215 | args = shlex.split(args) 216 | 217 | return Config(framework=framework, wdir=self.wdir_lineedit.text(), 218 | coverage=self.coverage_checkbox.isChecked(), args=args) 219 | 220 | 221 | def ask_for_config(frameworks, config, versions, parent=None): 222 | """ 223 | Ask user to specify a test configuration. 224 | 225 | This is a convenience function which displays a modal dialog window 226 | of type `ConfigDialog`. 227 | """ 228 | dialog = ConfigDialog(frameworks, config, versions, parent) 229 | result = dialog.exec_() 230 | if result == QDialog.Accepted: 231 | return dialog.get_config() 232 | 233 | 234 | if __name__ == '__main__': 235 | app = QApplication([]) 236 | frameworks = { 237 | 'nose2': object, 238 | 'unittest': object, 239 | 'pytest': object} 240 | versions = { 241 | 'nose2': {'available': False}, 242 | 'unittest': {'available': True}, 243 | 'pytest': {'available': True, 'plugins': {'pytest-cov', '3.1.4'}} 244 | } 245 | config = Config(wdir=getcwd()) 246 | print(ask_for_config(frameworks, config, versions)) 247 | -------------------------------------------------------------------------------- /spyder_unittest/backend/pytestrunner.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2013 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Support for pytest framework.""" 7 | 8 | from __future__ import annotations 9 | 10 | # Standard library imports 11 | import os 12 | import os.path as osp 13 | import re 14 | from typing import Any, Optional, TYPE_CHECKING 15 | 16 | # Local imports 17 | from spyder.config.base import get_translation 18 | from spyder_unittest.backend.runnerbase import (Category, RunnerBase, 19 | TestResult, COV_TEST_NAME) 20 | from spyder_unittest.backend.zmqreader import ZmqStreamReader 21 | if TYPE_CHECKING: 22 | from spyder_unittest.widgets.configdialog import Config 23 | 24 | try: 25 | _ = get_translation('spyder_unittest') 26 | except KeyError: 27 | import gettext 28 | _ = gettext.gettext 29 | 30 | 31 | class PyTestRunner(RunnerBase): 32 | """Class for running tests within pytest framework.""" 33 | 34 | module = 'pytest' 35 | name = 'pytest' 36 | 37 | def create_argument_list(self, config: Config, 38 | cov_path: Optional[str], 39 | single_test: Optional[str]) -> list[str]: 40 | """Create argument list for testing process.""" 41 | dirname = os.path.dirname(__file__) 42 | pyfile = os.path.join(dirname, 'workers', 'pytestworker.py') 43 | arguments = [pyfile, str(self.reader.port)] 44 | if config.coverage: 45 | arguments += [f'--cov={cov_path}', '--cov-report=term-missing'] 46 | if single_test: 47 | arguments.append(self.convert_testname_to_nodeid(single_test)) 48 | arguments += config.args 49 | return arguments 50 | 51 | def start(self, config: Config, cov_path: Optional[str], 52 | executable: str, pythonpath: list[str], 53 | single_test: Optional[str]) -> None: 54 | """Start process which will run the unit test suite.""" 55 | self.config = config 56 | self.reader = ZmqStreamReader() 57 | self.reader.sig_received.connect(self.process_output) 58 | super().start(config, cov_path, executable, pythonpath, single_test) 59 | 60 | def process_output(self, output: list[dict[str, Any]]) -> None: 61 | """ 62 | Process output of test process. 63 | 64 | Parameters 65 | ---------- 66 | output 67 | list of decoded Python object sent by test process. 68 | """ 69 | collected_list = [] 70 | collecterror_list = [] 71 | starttest_list = [] 72 | result_list = [] 73 | for result_item in output: 74 | if result_item['event'] == 'config': 75 | self.rootdir = result_item['rootdir'] 76 | elif result_item['event'] == 'collected': 77 | name = self.convert_nodeid_to_testname(result_item['nodeid']) 78 | collected_list.append(name) 79 | elif result_item['event'] == 'collecterror': 80 | tupl = self.logreport_collecterror_to_tuple(result_item) 81 | collecterror_list.append(tupl) 82 | elif result_item['event'] == 'starttest': 83 | name = self.logreport_starttest_to_str(result_item) 84 | starttest_list.append(name) 85 | elif result_item['event'] == 'logreport': 86 | testresult = self.logreport_to_testresult(result_item) 87 | result_list.append(testresult) 88 | 89 | if collected_list: 90 | self.sig_collected.emit(collected_list) 91 | if collecterror_list: 92 | self.sig_collecterror.emit(collecterror_list) 93 | if starttest_list: 94 | self.sig_starttest.emit(starttest_list) 95 | if result_list: 96 | self.sig_testresult.emit(result_list) 97 | 98 | def process_coverage(self, output: str) -> None: 99 | """Search the output text for coverage details. 100 | 101 | Called by the function 'finished' at the very end. 102 | """ 103 | cov_results = re.search( 104 | r'-*? coverage:.*?-*\nTOTAL\s.*?\s(\d*?)\%.*\n=*', 105 | output, flags=re.S) 106 | if cov_results: 107 | total_coverage = cov_results.group(1) 108 | cov_report = TestResult( 109 | Category.COVERAGE, f'{total_coverage}%', COV_TEST_NAME) 110 | # create a fake test, then emit the coverage as the result 111 | # This gives overall test coverage, used in TestDataModel.summary 112 | self.sig_collected.emit([COV_TEST_NAME]) 113 | self.sig_testresult.emit([cov_report]) 114 | 115 | # also build a result for each file's coverage 116 | header = "".join(cov_results.group(0).split("\n")[1:3]) 117 | # coverage report columns: 118 | # Name Stmts Miss Cover Missing 119 | for row in re.findall( 120 | r'^((.*?\.py) .*?(\d+%).*?(\d[\d\,\-\ ]*)?)$', 121 | cov_results.group(0), flags=re.M): 122 | lineno: Optional[int] = None 123 | if row[3]: 124 | match = re.search(r'^(\d*)', row[3]) 125 | if match: 126 | lineno = int(match.group(1)) - 1 127 | file_cov = TestResult( 128 | Category.COVERAGE, row[2], row[1], 129 | message=_('Missing: {}').format(row[3] if row[3] else _("(none)")), 130 | extra_text=_('{}\n{}').format(header, row[0]), filename=row[1], 131 | lineno=lineno) 132 | self.sig_collected.emit([row[1]]) 133 | self.sig_testresult.emit([file_cov]) 134 | 135 | def finished(self, exitcode: int) -> None: 136 | """ 137 | Called when the unit test process has finished. 138 | 139 | This function emits `sig_finished`. 140 | 141 | Parameters 142 | ---------- 143 | exitcode 144 | Exit code of the test process. 145 | """ 146 | self.reader.close() 147 | output = self.read_all_process_output() 148 | if self.config.coverage: 149 | self.process_coverage(output) 150 | normal_exit = exitcode in [0, 1, 2, 5] 151 | # Meaning of exit codes: 0 = all tests passed, 1 = test failed, 152 | # 2 = interrupted, 5 = no tests collected 153 | self.sig_finished.emit([], output, normal_exit) 154 | 155 | def normalize_module_name(self, name: str) -> str: 156 | """ 157 | Convert module name reported by pytest to Python conventions. 158 | 159 | This function strips the .py suffix and replaces '/' by '.', so that 160 | 'ham/spam.py' becomes 'ham.spam'. 161 | 162 | The result is relative to the directory from which tests are run and 163 | not the pytest root dir. 164 | """ 165 | wdir = osp.realpath(self.config.wdir) 166 | if wdir != self.rootdir: 167 | abspath = osp.join(self.rootdir, name) 168 | try: 169 | name = osp.relpath(abspath, start=wdir) 170 | except ValueError: 171 | # Happens on Windows if paths are on different drives 172 | pass 173 | 174 | if name.endswith('.py'): 175 | name = name[:-3] 176 | return name.replace(osp.sep, '.') 177 | 178 | def convert_nodeid_to_testname(self, nodeid: str) -> str: 179 | """Convert a nodeid to a test name.""" 180 | module, name = nodeid.split('::', 1) 181 | module = self.normalize_module_name(module) 182 | return '{}.{}'.format(module, name) 183 | 184 | def convert_testname_to_nodeid(self, testname: str) -> str: 185 | """ 186 | Convert a test name to a nodeid relative to wdir. 187 | 188 | A true nodeid is relative to the pytest root dir. The return value of 189 | this function is like a nodeid but relative to the wdir (i.e., the 190 | directory from which test are run). This is the format that pytest 191 | expects when running single tests. 192 | """ 193 | *path_parts, last_part = testname.split('.') 194 | path_parts[-1] += '.py' 195 | nodeid = osp.join(*path_parts) + '::' + last_part 196 | return nodeid 197 | 198 | def logreport_collecterror_to_tuple( 199 | self, report: dict[str, Any]) -> tuple[str, str]: 200 | """Convert a 'collecterror' logreport to a (str, str) tuple.""" 201 | module = self.normalize_module_name(report['nodeid']) 202 | return (module, report['longrepr']) 203 | 204 | def logreport_starttest_to_str(self, report: dict[str, Any]) -> str: 205 | """Convert a 'starttest' logreport to a str.""" 206 | return self.convert_nodeid_to_testname(report['nodeid']) 207 | 208 | def logreport_to_testresult(self, report: dict[str, Any]) -> TestResult: 209 | """Convert a logreport sent by test process to a TestResult.""" 210 | status = report['outcome'] 211 | if report['outcome'] in ('failed', 'xpassed') or report['witherror']: 212 | cat = Category.FAIL 213 | elif report['outcome'] in ('passed', 'xfailed'): 214 | cat = Category.OK 215 | else: 216 | cat = Category.SKIP 217 | testname = self.convert_nodeid_to_testname(report['nodeid']) 218 | message = report.get('message', '') 219 | extra_text = report.get('longrepr', '') 220 | if 'sections' in report: 221 | if extra_text: 222 | extra_text += '\n' 223 | for (heading, text) in report['sections']: 224 | extra_text += '----- {} -----\n{}'.format(heading, text) 225 | filename = osp.join(self.rootdir, report['filename']) 226 | result = TestResult(cat, status, testname, message=message, 227 | time=report['duration'], extra_text=extra_text, 228 | filename=filename, lineno=report['lineno']) 229 | return result 230 | -------------------------------------------------------------------------------- /spyder_unittest/widgets/tests/test_datatree.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Copyright © 2017 Spyder Project Contributors 4 | # Licensed under the terms of the MIT License 5 | # (see LICENSE.txt for details) 6 | """Tests for unittestgui.py.""" 7 | 8 | # Third party imports 9 | from qtpy.QtCore import QModelIndex, QPoint, Qt 10 | from qtpy.QtGui import QBrush, QColor, QContextMenuEvent 11 | from unittest.mock import Mock 12 | import pytest 13 | 14 | # Local imports 15 | from spyder_unittest.backend.runnerbase import Category, TestResult 16 | from spyder_unittest.widgets.datatree import ( 17 | COLORS, TestDataModel, TestDataView) 18 | 19 | 20 | @pytest.fixture 21 | def view_and_model(qtbot): 22 | view = TestDataView() 23 | model = TestDataModel() 24 | # setModel() before populating testresults because setModel() does a sort 25 | view.setModel(model) 26 | res = [TestResult(Category.OK, 'status', 'foo.bar'), 27 | TestResult(Category.FAIL, 'error', 'foo.bar', 'kadoom', 0, 28 | 'crash!\nboom!', filename='ham.py', lineno=42)] 29 | model.testresults = res 30 | return view, model 31 | 32 | def test_contextMenuEvent_calls_exec(view_and_model, monkeypatch): 33 | # test that a menu is displayed when clicking on an item 34 | mock_exec = Mock() 35 | monkeypatch.setattr('spyder_unittest.widgets.datatree.QMenu.exec_', mock_exec) 36 | view, model = view_and_model 37 | pos = view.visualRect(model.index(0, 0)).center() 38 | event = QContextMenuEvent(QContextMenuEvent.Mouse, pos) 39 | view.contextMenuEvent(event) 40 | assert mock_exec.called 41 | 42 | # test that no menu is displayed when clicking below the bottom item 43 | mock_exec.reset_mock() 44 | pos = view.visualRect(model.index(1, 0)).bottomRight() 45 | pos += QPoint(0, 1) 46 | event = QContextMenuEvent(QContextMenuEvent.Mouse, pos) 47 | view.contextMenuEvent(event) 48 | assert not mock_exec.called 49 | 50 | def test_go_to_test_definition_with_invalid_target(view_and_model, qtbot): 51 | view, model = view_and_model 52 | with qtbot.assertNotEmitted(view.sig_edit_goto): 53 | view.go_to_test_definition(model.index(0, 0)) 54 | 55 | def test_go_to_test_definition_with_valid_target(view_and_model, qtbot): 56 | view, model = view_and_model 57 | with qtbot.waitSignal(view.sig_edit_goto) as blocker: 58 | view.go_to_test_definition(model.index(1, 0)) 59 | assert blocker.args == ['ham.py', 42] 60 | 61 | def test_go_to_test_definition_with_lineno_none(view_and_model, qtbot): 62 | view, model = view_and_model 63 | res = model.testresults 64 | res[1].lineno = None 65 | model.testresults = res 66 | with qtbot.waitSignal(view.sig_edit_goto) as blocker: 67 | view.go_to_test_definition(model.index(1, 0)) 68 | assert blocker.args == ['ham.py', 0] 69 | 70 | def test_run_single_test(view_and_model, qtbot): 71 | view, model = view_and_model 72 | with qtbot.waitSignal(view.sig_single_test_run_requested) as blocker: 73 | view.run_single_test(model.index(1, 0)) 74 | assert blocker.args == ['foo.bar'] 75 | 76 | def test_make_index_canonical_with_index_in_column2(view_and_model): 77 | view, model = view_and_model 78 | index = model.index(1, 2) 79 | res = view.make_index_canonical(index) 80 | assert res == model.index(1, 0) 81 | 82 | def test_make_index_canonical_with_level2_index(view_and_model): 83 | view, model = view_and_model 84 | index = model.index(1, 0, model.index(1, 0)) 85 | res = view.make_index_canonical(index) 86 | assert res == model.index(1, 0) 87 | 88 | def test_make_index_canonical_with_invalid_index(view_and_model): 89 | view, model = view_and_model 90 | index = QModelIndex() 91 | res = view.make_index_canonical(index) 92 | assert res is None 93 | 94 | def test_build_context_menu(view_and_model): 95 | view, model = view_and_model 96 | menu = view.build_context_menu(model.index(0, 0)) 97 | assert len(menu.actions()) == 3 98 | assert menu.actions()[0].text() == 'Expand' 99 | assert menu.actions()[1].text() == 'Go to definition' 100 | assert menu.actions()[2].text() == 'Run only this test' 101 | 102 | def test_build_context_menu_with_disabled_entries(view_and_model): 103 | view, model = view_and_model 104 | menu = view.build_context_menu(model.index(0, 0)) 105 | assert menu.actions()[0].isEnabled() == False 106 | assert menu.actions()[1].isEnabled() == False 107 | assert menu.actions()[2].isEnabled() == True 108 | 109 | def test_build_context_menu_with_enabled_entries(view_and_model): 110 | view, model = view_and_model 111 | menu = view.build_context_menu(model.index(1, 0)) 112 | assert menu.actions()[0].isEnabled() == True 113 | assert menu.actions()[1].isEnabled() == True 114 | assert menu.actions()[2].isEnabled() == True 115 | 116 | def test_build_context_menu_with_coverage_entry(view_and_model): 117 | view, model = view_and_model 118 | testresult = TestResult(Category.COVERAGE, 'coverage', 'foo') 119 | model.testresults.append(testresult) 120 | menu = view.build_context_menu(model.index(2, 0)) 121 | assert menu.actions()[0].isEnabled() == False 122 | assert menu.actions()[1].isEnabled() == False 123 | assert menu.actions()[2].isEnabled() == False 124 | 125 | def test_build_context_menu_with_expanded_entry(view_and_model): 126 | view, model = view_and_model 127 | view.expand(model.index(1, 0)) 128 | menu = view.build_context_menu(model.index(1, 0)) 129 | assert menu.actions()[0].text() == 'Collapse' 130 | assert menu.actions()[0].isEnabled() == True 131 | 132 | def test_testdatamodel_using_qtmodeltester(qtmodeltester): 133 | model = TestDataModel() 134 | res = [TestResult(Category.OK, 'status', 'foo.bar'), 135 | TestResult(Category.FAIL, 'error', 'foo.bar', 'kadoom', 0, 136 | 'crash!\nboom!')] 137 | model.testresults = res 138 | qtmodeltester.check(model) 139 | 140 | @pytest.mark.parametrize('config, result', 141 | [(False, 'foo.bar'), (True, 'f.bar')]) 142 | def test_testdatamodel_shows_abbreviated_name_in_table(qtbot, config, result): 143 | model = TestDataModel() 144 | old_config = model.get_conf('abbrev_test_names') 145 | model.set_conf('abbrev_test_names', config) 146 | res = TestResult(Category.OK, 'status', 'foo.bar', '', 0, '') 147 | model.testresults = [res] 148 | index = model.index(0, 1) 149 | assert model.data(index, Qt.DisplayRole) == result 150 | model.set_conf('abbrev_test_names', old_config) 151 | 152 | def test_testdatamodel_shows_full_name_in_tooltip(qtbot): 153 | model = TestDataModel() 154 | res = TestResult(Category.OK, 'status', 'foo.bar', '', 0, '') 155 | model.testresults = [res] 156 | index = model.index(0, 1) 157 | assert model.data(index, Qt.ToolTipRole) == 'foo.bar' 158 | 159 | def test_testdatamodel_shows_time(qtmodeltester): 160 | model = TestDataModel() 161 | res = TestResult(Category.OK, 'status', 'foo.bar', time=0.0012345) 162 | model.testresults = [res] 163 | index = model.index(0, 3) 164 | assert model.data(index, Qt.DisplayRole) == '1.23' 165 | assert model.data(index, Qt.TextAlignmentRole) == Qt.AlignRight 166 | 167 | def test_testdatamodel_shows_time_when_zero(qtmodeltester): 168 | model = TestDataModel() 169 | res = TestResult(Category.OK, 'status', 'foo.bar', time=0) 170 | model.testresults = [res] 171 | assert model.data(model.index(0, 3), Qt.DisplayRole) == '0.00' 172 | 173 | def test_testdatamodel_shows_time_when_blank(qtmodeltester): 174 | model = TestDataModel() 175 | res = TestResult(Category.OK, 'status', 'foo.bar') 176 | model.testresults = [res] 177 | assert model.data(model.index(0, 3), Qt.DisplayRole) == '' 178 | 179 | def test_testdatamodel_data_background(): 180 | model = TestDataModel() 181 | res = [TestResult(Category.OK, 'status', 'foo.bar'), 182 | TestResult(Category.FAIL, 'error', 'foo.bar', 'kadoom')] 183 | model.testresults = res 184 | index = model.index(0, 0) 185 | expected = QBrush(QColor(COLORS[Category.OK])) 186 | assert model.data(index, Qt.BackgroundRole) == expected 187 | index = model.index(1, 2) 188 | expected = QBrush(QColor(COLORS[Category.FAIL])) 189 | assert model.data(index, Qt.BackgroundRole) == expected 190 | 191 | def test_testdatamodel_data_userrole(): 192 | model = TestDataModel() 193 | res = [TestResult(Category.OK, 'status', 'foo.bar', filename='somefile', 194 | lineno=42)] 195 | model.testresults = res 196 | index = model.index(0, 0) 197 | assert model.data(index, Qt.UserRole) == ('somefile', 42) 198 | 199 | def test_testdatamodel_add_tests(qtbot): 200 | def check_args1(parent, begin, end): 201 | return not parent.isValid() and begin == 0 and end == 0 202 | 203 | def check_args2(parent, begin, end): 204 | return not parent.isValid() and begin == 1 and end == 1 205 | 206 | model = TestDataModel() 207 | assert model.testresults == [] 208 | 209 | result1 = TestResult(Category.OK, 'status', 'foo.bar') 210 | with qtbot.waitSignals([model.rowsInserted, model.sig_summary], 211 | check_params_cbs=[check_args1, None], 212 | raising=True): 213 | model.add_testresults([result1]) 214 | assert model.testresults == [result1] 215 | 216 | result2 = TestResult(Category.FAIL, 'error', 'foo.bar', 'kadoom') 217 | with qtbot.waitSignals([model.rowsInserted, model.sig_summary], 218 | check_params_cbs=[check_args2, None], 219 | raising=True): 220 | model.add_testresults([result2]) 221 | assert model.testresults == [result1, result2] 222 | 223 | 224 | def test_testdatamodel_replace_tests(qtbot): 225 | def check_args(topLeft, bottomRight, *args): 226 | return (topLeft.row() == 0 227 | and topLeft.column() == 0 228 | and not topLeft.parent().isValid() 229 | and bottomRight.row() == 0 230 | and bottomRight.column() == 3 231 | and not bottomRight.parent().isValid()) 232 | 233 | model = TestDataModel() 234 | result1 = TestResult(Category.OK, 'status', 'foo.bar') 235 | model.testresults = [result1] 236 | result2 = TestResult(Category.FAIL, 'error', 'foo.bar', 'kadoom') 237 | with qtbot.waitSignals([model.dataChanged, model.sig_summary], 238 | check_params_cbs=[check_args, None], 239 | raising=True): 240 | model.update_testresults([result2]) 241 | assert model.testresults == [result2] 242 | 243 | STANDARD_TESTRESULTS = [ 244 | TestResult(Category.OK, 'status', 'foo.bar', time=2), 245 | TestResult(Category.FAIL, 'failure', 'fu.baz', 'kaboom',time=1), 246 | TestResult(Category.FAIL, 'error', 'fu.bar', 'boom')] 247 | 248 | def test_testdatamodel_sort_by_status_ascending(qtbot): 249 | model = TestDataModel() 250 | model.testresults = STANDARD_TESTRESULTS[:] 251 | with qtbot.waitSignal(model.modelReset): 252 | model.sort(0, Qt.AscendingOrder) 253 | expected = [STANDARD_TESTRESULTS[k] for k in [2, 1, 0]] 254 | assert model.testresults == expected 255 | 256 | def test_testdatamodel_sort_by_status_descending(): 257 | model = TestDataModel() 258 | model.testresults = STANDARD_TESTRESULTS[:] 259 | model.sort(0, Qt.DescendingOrder) 260 | expected = [STANDARD_TESTRESULTS[k] for k in [0, 1, 2]] 261 | assert model.testresults == expected 262 | 263 | def test_testdatamodel_sort_by_name(): 264 | model = TestDataModel() 265 | model.testresults = STANDARD_TESTRESULTS[:] 266 | model.sort(1, Qt.AscendingOrder) 267 | expected = [STANDARD_TESTRESULTS[k] for k in [0, 2, 1]] 268 | assert model.testresults == expected 269 | 270 | def test_testdatamodel_sort_by_message(): 271 | model = TestDataModel() 272 | model.testresults = STANDARD_TESTRESULTS[:] 273 | model.sort(2, Qt.AscendingOrder) 274 | expected = [STANDARD_TESTRESULTS[k] for k in [0, 2, 1]] 275 | assert model.testresults == expected 276 | 277 | def test_testdatamodel_sort_by_time(): 278 | model = TestDataModel() 279 | model.testresults = STANDARD_TESTRESULTS[:] 280 | model.sort(3, Qt.AscendingOrder) 281 | expected = [STANDARD_TESTRESULTS[k] for k in [2, 1, 0]] 282 | assert model.testresults == expected 283 | --------------------------------------------------------------------------------