Skip to content

Commit

Permalink
Merge pull request #208 from jitseniesen/config-abbrev
Browse files Browse the repository at this point in the history
PR: Add config option to abbreviate test names
  • Loading branch information
jitseniesen authored Jun 21, 2023
2 parents 8ced761 + 8042661 commit c30c21d
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 15 deletions.
29 changes: 23 additions & 6 deletions spyder_unittest/unittestplugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os.path as osp

# Third party imports
import qtawesome
from qtpy.QtCore import Qt

# Spyder imports
Expand All @@ -19,9 +20,11 @@
from spyder.config.base import get_translation
from spyder.config.gui import is_dark_interface
from spyder.plugins.mainmenu.api import ApplicationMenus
from spyder.utils.palette import SpyderPalette

# Local imports
from spyder_unittest.widgets.configdialog import Config
from spyder_unittest.widgets.confpage import UnitTestConfigPage
from spyder_unittest.widgets.unittestgui import UnitTestWidget

_ = get_translation('spyder_unittest')
Expand All @@ -44,13 +47,17 @@ class UnitTestPlugin(SpyderDockablePlugin):

CONF_SECTION = NAME
CONF_DEFAULTS = [(CONF_SECTION,
{'framework': '', 'wdir': '', 'coverage': False}),
{'framework': '',
'wdir': '',
'coverage': False,
'abbrev_test_names': False}),
('shortcuts',
{'unittest/Run tests': 'Alt+Shift+F11'})]
CONF_NAMEMAP = {CONF_SECTION: [(CONF_SECTION,
['framework', 'wdir', 'coverage'])]}
CONF_FILE = True
CONF_VERSION = '0.2.0'
CONF_WIDGET_CLASS = UnitTestConfigPage

# --- Mandatory SpyderDockablePlugin methods ------------------------------

Expand Down Expand Up @@ -86,20 +93,19 @@ def get_icon(self):
QIcon
QIcon instance
"""
return self.create_icon('profiler')
return qtawesome.icon('mdi.test-tube', color=SpyderPalette.ICON_1)

def on_initialize(self):
"""
Setup the plugin.
"""
self.update_pythonpath()
self.get_widget().sig_newconfig.connect(self.save_config)

self.create_action(
UnitTestPluginActions.Run,
text=_('Run unit tests'),
tip=_('Run unit tests'),
icon=self.create_icon('profiler'),
icon=self.get_icon(),
triggered=self.maybe_configure_and_start,
context=Qt.ApplicationShortcut,
register_shortcut=True)
Expand Down Expand Up @@ -154,11 +160,22 @@ def on_preferences_available(self):
"""
Use config when Preferences plugin available.
Specifically, find out whether Spyder uses a dark interface and
communicate this to the unittest widget.
Specifically, register the unittest plugin preferences, and find out
whether Spyder uses a dark interface and communicate this to the
unittest widget.
"""
preferences = self.get_plugin(Plugins.Preferences)
preferences.register_plugin_preferences(self)
self.get_widget().use_dark_interface(is_dark_interface())

@on_plugin_teardown(plugin=Plugins.Preferences)
def on_preferences_teardown(self):
"""
De-register unittest plugin preferences.
"""
preferences = self.get_plugin(Plugins.Preferences)
preferences.deregister_plugin_preferences(self)

@on_plugin_available(plugin=Plugins.Projects)
def on_projects_available(self):
"""
Expand Down
37 changes: 37 additions & 0 deletions spyder_unittest/widgets/confpage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
#
# -----------------------------------------------------------------------------
# Copyright (c) 2023- Spyder Project Contributors
#
# Released under the terms of the MIT License
# (see LICENSE.txt in the project root directory for details)
# -----------------------------------------------------------------------------

"""
Spyder-unittest Preferences Page.
"""

# Third party imports
from qtpy.QtWidgets import QGroupBox, QVBoxLayout
from spyder.api.preferences import PluginConfigPage
from spyder.api.translations import get_translation

# Localization
_ = get_translation('spyder_unittest')


class UnitTestConfigPage(PluginConfigPage):

def setup_page(self) -> None:
settings_group = QGroupBox(_('Settings'))
self.abbrev_box = self.create_checkbox(
_('Abbreviate test names'), 'abbrev_test_names', default=False)

settings_layout = QVBoxLayout()
settings_layout.addWidget(self.abbrev_box)
settings_group.setLayout(settings_layout)

vlayout = QVBoxLayout()
vlayout.addWidget(settings_group)
vlayout.addStretch(1)
self.setLayout(vlayout)
13 changes: 10 additions & 3 deletions spyder_unittest/widgets/datatree.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from qtpy.QtCore import QAbstractItemModel, QModelIndex, Qt, Signal
from qtpy.QtGui import QBrush, QColor, QFont
from qtpy.QtWidgets import QMenu, QTreeView
from spyder.api.config.mixins import SpyderConfigurationAccessor
from spyder.config.base import get_translation
from spyder.utils.palette import QStylePalette, SpyderPalette
from spyder.utils.qthelpers import create_action
Expand Down Expand Up @@ -179,7 +180,7 @@ def spanFirstColumn(self, firstRow, lastRow):
self.setFirstColumnSpanned(i, index, True)


class TestDataModel(QAbstractItemModel):
class TestDataModel(QAbstractItemModel, SpyderConfigurationAccessor):
"""
Model class storing test results for display.
Expand All @@ -202,6 +203,8 @@ class TestDataModel(QAbstractItemModel):
Emitted with new summary if test results change.
"""

CONF_SECTION = 'unittest'

sig_summary = Signal(str)
__test__ = False # this is not a pytest test class

Expand Down Expand Up @@ -314,10 +317,14 @@ def data(self, index, role):
elif column == STATUS_COLUMN:
return self.testresults[row].status
elif column == NAME_COLUMN:
name = self.testresults[row].name
# don't abbreviate for the code coverage filename
if self.testresults[row].category == Category.COVERAGE:
return self.testresults[row].name
return self.abbreviator.abbreviate(self.testresults[row].name)
return name
if self.get_conf('abbrev_test_names', False):
return self.abbreviator.abbreviate(name)
else:
return name
elif column == MESSAGE_COLUMN:
return self.testresults[row].message
elif column == TIME_COLUMN:
Expand Down
172 changes: 172 additions & 0 deletions spyder_unittest/widgets/tests/test_confpage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) 2023- Spyder Project Contributors
#
# Licensed under the terms of the MIT License
# (see LICENSE.txt for details)
# -----------------------------------------------------------------------------

# Standard library imports
import sys
import types
from unittest.mock import Mock, MagicMock

# Third party imports
from qtpy.QtWidgets import QWidget, QMainWindow
import pytest

# Spyder imports
from spyder.api.plugins import Plugins
from spyder.api.plugin_registration.registry import PLUGIN_REGISTRY
from spyder.app.cli_options import get_options
from spyder.config.manager import CONF
from spyder.plugins.preferences.plugin import Preferences

# Local imports
from spyder_unittest.unittestplugin import UnitTestPlugin


# -----------------------------------------------------------------------------
#
# Classes and fixtures copied from spyder/plugins/preferences/tests/conftest.py

class MainWindowMock(QMainWindow):
register_shortcut = Mock()

def __init__(self, parent):
super().__init__(parent)
self.default_style = None
self.widgetlist = []
self.thirdparty_plugins = []
self.shortcut_data = []
self.prefs_dialog_instance = None
self._APPLICATION_TOOLBARS = MagicMock()

self.console = Mock()

# To provide command line options for plugins that need them
sys_argv = [sys.argv[0]] # Avoid options passed to pytest
self._cli_options = get_options(sys_argv)[0]

PLUGIN_REGISTRY.reset()
PLUGIN_REGISTRY.sig_plugin_ready.connect(self.register_plugin)
PLUGIN_REGISTRY.register_plugin(self, Preferences)

# Load shortcuts for tests
for context, name, __ in CONF.iter_shortcuts():
self.shortcut_data.append((None, context, name, None, None))

for attr in ['mem_status', 'cpu_status']:
mock_attr = Mock()
setattr(mock_attr, 'toolTip', lambda: '')
setattr(mock_attr, 'setToolTip', lambda x: '')
setattr(mock_attr, 'prefs_dialog_instance', lambda: '')
setattr(self, attr, mock_attr)

def register_plugin(self, plugin_name, external=False):
plugin = PLUGIN_REGISTRY.get_plugin(plugin_name)
plugin._register(omit_conf=True)

def get_plugin(self, plugin_name, error=True):
if plugin_name in PLUGIN_REGISTRY:
return PLUGIN_REGISTRY.get_plugin(plugin_name)

def set_prefs_size(self, size):
pass


class ConfigDialogTester(QWidget):
def __init__(self, parent, main_class,
general_config_plugins, plugins):
super().__init__(parent)
self._main = main_class(self) if main_class else None
if self._main is None:
self._main = MainWindowMock(self)

def set_prefs_size(self, size):
pass

def register_plugin(self, plugin_name, external=False):
plugin = PLUGIN_REGISTRY.get_plugin(plugin_name)
plugin._register()

def get_plugin(self, plugin_name, error=True):
if plugin_name in PLUGIN_REGISTRY:
return PLUGIN_REGISTRY.get_plugin(plugin_name)
return None

# Commented out because it gives the error:
# A plugin with section "unittest" already exists!
# setattr(self._main, 'register_plugin',
# types.MethodType(register_plugin, self._main))
setattr(self._main, 'get_plugin',
types.MethodType(get_plugin, self._main))
setattr(self._main, 'set_prefs_size',
types.MethodType(set_prefs_size, self._main))

PLUGIN_REGISTRY.reset()
PLUGIN_REGISTRY.sig_plugin_ready.connect(self._main.register_plugin)
print(f'ConfigDialogTester registering {Preferences=}')
PLUGIN_REGISTRY.register_plugin(self._main, Preferences)

if plugins:
for Plugin in plugins:
if hasattr(Plugin, 'CONF_WIDGET_CLASS'):
for required in (Plugin.REQUIRES or []):
if required not in PLUGIN_REGISTRY:
PLUGIN_REGISTRY.plugin_registry[required] = MagicMock()

PLUGIN_REGISTRY.register_plugin(self._main, Plugin)
else:
plugin = Plugin(self._main)
preferences = self._main.get_plugin(Plugins.Preferences)
preferences.register_plugin_preferences(plugin)


@pytest.fixture
def config_dialog(qtbot, request):
# mocker.patch.object(ima, 'icon', lambda x, *_: QIcon())
# Above line commented out from source because it gave an error

main_class, general_config_plugins, plugins = request.param

main_ref = ConfigDialogTester(
None, main_class, general_config_plugins, plugins)
qtbot.addWidget(main_ref)

preferences = main_ref._main.get_plugin(Plugins.Preferences)
preferences.open_dialog(None)
container = preferences.get_container()
dlg = container.dialog

yield dlg

dlg.close()


# -----------------------------------------------------------------------------
#
# Test for the spyder-unittest plugin

@pytest.mark.parametrize(
'config_dialog',
[[MainWindowMock, [], [UnitTestPlugin]]],
indirect=True)
def test_unittestconfigpage(config_dialog):
"""Test that changing "Abbreviate test names" works as expected."""
# Get reference to Preferences dialog and widget page to interact with
dlg = config_dialog
widget = config_dialog.get_page()

# Assert default value of option in True
assert widget.get_option('abbrev_test_names') is False

# Toggle checkbox and assert that option value is now False
widget.abbrev_box.click()
dlg.apply_btn.click()
assert widget.get_option('abbrev_test_names') is True

# Reset options to default and check that option value is True again
# Note: it is necessary to specify the section in reset_to_defaults()
CONF.reset_to_defaults(section='unittest', notification=False)
assert widget.get_option('abbrev_test_names') is False
9 changes: 7 additions & 2 deletions spyder_unittest/widgets/tests/test_datatree.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,17 @@ def test_testdatamodel_using_qtmodeltester(qtmodeltester):
model.testresults = res
qtmodeltester.check(model)

def test_testdatamodel_shows_abbreviated_name_in_table(qtbot):
@pytest.mark.parametrize('config, result',
[(False, 'foo.bar'), (True, 'f.bar')])
def test_testdatamodel_shows_abbreviated_name_in_table(qtbot, config, result):
model = TestDataModel()
old_config = model.get_conf('abbrev_test_names')
model.set_conf('abbrev_test_names', config)
res = TestResult(Category.OK, 'status', 'foo.bar', '', 0, '')
model.testresults = [res]
index = model.index(0, 1)
assert model.data(index, Qt.DisplayRole) == 'f.bar'
assert model.data(index, Qt.DisplayRole) == result
model.set_conf('abbrev_test_names', old_config)

def test_testdatamodel_shows_full_name_in_tooltip(qtbot):
model = TestDataModel()
Expand Down
8 changes: 4 additions & 4 deletions spyder_unittest/widgets/tests/test_unittestgui.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,11 @@ def test_run_tests_and_display_results(qtbot, widget, tmpdir, monkeypatch, frame
assert model.rowCount() == 2
assert model.index(0, 0).data(
Qt.DisplayRole) == 'failure' if framework == 'nose2' else 'failed'
assert model.index(0, 1).data(Qt.DisplayRole) == 't.test_fail'
assert model.index(0, 1).data(Qt.DisplayRole) == 'test_foo.test_fail'
assert model.index(0, 1).data(Qt.ToolTipRole) == 'test_foo.test_fail'
assert model.index(1, 0).data(
Qt.DisplayRole) == 'ok' if framework == 'nose2' else 'passed'
assert model.index(1, 1).data(Qt.DisplayRole) == 't.test_ok'
assert model.index(1, 1).data(Qt.DisplayRole) == 'test_foo.test_ok'
assert model.index(1, 1).data(Qt.ToolTipRole) == 'test_foo.test_ok'
assert model.index(1, 2).data(Qt.DisplayRole) == ''

Expand Down Expand Up @@ -207,10 +207,10 @@ def test_run_tests_using_unittest_and_display_results(
model = widget.testdatamodel
assert model.rowCount() == 2
assert model.index(0, 0).data(Qt.DisplayRole) == 'failure'
assert model.index(0, 1).data(Qt.DisplayRole) == 't.M.test_fail'
assert model.index(0, 1).data(Qt.DisplayRole) == 'test_foo.MyTest.test_fail'
assert model.index(0, 1).data(Qt.ToolTipRole) == 'test_foo.MyTest.test_fail'
assert model.index(1, 0).data(Qt.DisplayRole) == 'success'
assert model.index(1, 1).data(Qt.DisplayRole) == 't.M.test_ok'
assert model.index(1, 1).data(Qt.DisplayRole) == 'test_foo.MyTest.test_ok'
assert model.index(1, 1).data(Qt.ToolTipRole) == 'test_foo.MyTest.test_ok'
assert model.index(1, 2).data(Qt.DisplayRole) == ''

Expand Down

0 comments on commit c30c21d

Please sign in to comment.