Hello community,

here is the log from the commit of package python-spyder-unittest for 
openSUSE:Factory checked in at 2020-04-02 17:42:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-spyder-unittest (Old)
 and      /work/SRC/openSUSE:Factory/.python-spyder-unittest.new.3248 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-spyder-unittest"

Thu Apr  2 17:42:58 2020 rev:4 rq:790074 version:0.4.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-spyder-unittest/python-spyder-unittest.changes
    2018-09-20 11:42:05.128834506 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-spyder-unittest.new.3248/python-spyder-unittest.changes
  2020-04-02 17:43:00.437379852 +0200
@@ -1,0 +2,26 @@
+Tue Mar 24 16:06:07 UTC 2020 - Benjamin Greiner <[email protected]>
+
+- add release to provides and obsoletes
+
+-------------------------------------------------------------------
+Mon Mar 23 14:00:10 UTC 2020 - Benjamin Greiner <[email protected]>
+
+- spyder3 is spyder again 
+- enable unit tests
+- no python2 package because spyder does not provide one either
+
+-------------------------------------------------------------------
+Thu Jan 30 15:37:08 UTC 2020 - Todd R <[email protected]>
+
+- Update to 0.4.0
+  + Issues Closed
+    * Colours make text hard to read when run in dark mode
+    * Docstrings in test functions confuse unittest's output parser
+    * KeyError: 'test not found'
+  + Pull Requests Merged
+    * PR: Use appropriate colours when Spyder is in dark mode
+    * PR: Allow for unittest tests to have docstrings
+    * PR: Use nodeid provided by pytest in itemcollected hook
+    * PR: Compatibility fixes for Spyder 4
+
+-------------------------------------------------------------------

Old:
----
  spyder_unittest-0.3.1.tar.gz

New:
----
  spyder_unittest-0.4.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-spyder-unittest.spec ++++++
--- /var/tmp/diff_new_pack.NrTU2z/_old  2020-04-02 17:43:03.201381993 +0200
+++ /var/tmp/diff_new_pack.NrTU2z/_new  2020-04-02 17:43:03.205381996 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-spyder-unittest
 #
-# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -12,25 +12,39 @@
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
 
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
+%define skip_python2 1
 Name:           python-spyder-unittest
-Version:        0.3.1
+Version:        0.4.0
 Release:        0
 Summary:        Plugin to run tests from within the Spyder IDE
 License:        MIT
 Group:          Development/Languages/Python
-Url:            https://github.com/spyder-ide/spyder-unittest
-Source:         
https://files.pythonhosted.org/packages/source/s/spyder-unittest/spyder_unittest-%{version}.tar.gz
+URL:            https://github.com/spyder-ide/spyder-unittest
+Source:         
https://files.pythonhosted.org/packages/source/s/spyder_unittest/spyder_unittest-%{version}.tar.gz
 BuildRequires:  %{python_module devel}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
+Requires:       python-lxml
+Requires:       spyder >= 3
+Provides:       spyder3-unittest = %{version}-%{release}
+Obsoletes:      spyder3-unittest < %{version}-%{release}
 BuildArch:      noarch
-
+# SECTION test requirements
+BuildRequires:  %{python_module QtPy}
+BuildRequires:  %{python_module lxml}
+BuildRequires:  %{python_module nose}
+BuildRequires:  %{python_module pytest-qt}
+BuildRequires:  %{python_module pytest-xvfb}
+BuildRequires:  %{python_module pytest}
+BuildRequires:  spyder >= 3
+BuildRequires:  xdpyinfo
+# /SECTION
 %python_subpackages
 
 %description
@@ -40,32 +54,6 @@
 This is a plugin for the Spyder IDE that integrates unit test
 frameworks. It allows running tests and viewing the results.
 
-%package -n spyder-unittest
-Summary:        Plugin to run tests from within the Spyder IDE
-Group:          Development/Languages/Python
-Requires:       python-lxml
-Requires:       spyder >= 3
-
-%description -n spyder-unittest
-Spyder, the Scientific Python Development Environment, is an
-IDE for researchers, engineers and data analysts.
-
-This is a plugin for the Spyder IDE that integrates unit test
-frameworks. It allows running tests and viewing the results.
-
-%package -n spyder3-unittest
-Summary:        Plugin to run tests from within the Spyder3 IDE
-Group:          Development/Languages/Python
-Requires:       python3-lxml
-Requires:       spyder3 >= 3
-
-%description -n spyder3-unittest
-Spyder, the Scientific Python Development Environment, is an
-IDE for researchers, engineers and data analysts.
-
-This is a plugin for the Spyder IDE that integrates unit test
-frameworks. It allows running tests and viewing the results.
-
 %prep
 %setup -q -n spyder_unittest-%{version}
 
@@ -76,16 +64,12 @@
 %python_install
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
 
-%files -n spyder-unittest
-%defattr(-,root,root,-)
-%doc CHANGELOG.md README.md
-%license LICENSE.txt
-%{python2_sitelib}/*
+%check
+%pytest
 
-%files -n spyder3-unittest
-%defattr(-,root,root,-)
+%files %{python_files}
 %doc CHANGELOG.md README.md
 %license LICENSE.txt
-%{python3_sitelib}/*
+%{python_sitelib}/*
 
 %changelog

++++++ spyder_unittest-0.3.1.tar.gz -> spyder_unittest-0.4.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/spyder_unittest-0.3.1/CHANGELOG.md 
new/spyder_unittest-0.4.0/CHANGELOG.md
--- old/spyder_unittest-0.3.1/CHANGELOG.md      2018-06-15 23:06:16.000000000 
+0200
+++ new/spyder_unittest-0.4.0/CHANGELOG.md      2020-01-07 13:37:43.000000000 
+0100
@@ -1,5 +1,27 @@
 # History of changes
 
+## Version 0.4.0 (2020/01/07)
+
+This release updates the plugin to be used with Spyder 4 and fixes some bugs.
+
+### Issues Closed
+
+* [Issue 133](https://github.com/spyder-ide/spyder-unittest/issues/133) - 
Colours make text hard to read when run in dark mode ([PR 
135](https://github.com/spyder-ide/spyder-unittest/pull/135))
+* [Issue 129](https://github.com/spyder-ide/spyder-unittest/issues/129) - 
Docstrings in test functions confuse unittest's output parser ([PR 
134](https://github.com/spyder-ide/spyder-unittest/pull/134))
+* [Issue 128](https://github.com/spyder-ide/spyder-unittest/issues/128) - 
KeyError: 'test not found' ([PR 
132](https://github.com/spyder-ide/spyder-unittest/pull/132))
+
+In this release 3 issues were closed.
+
+### Pull Requests Merged
+
+* [PR 135](https://github.com/spyder-ide/spyder-unittest/pull/135) - PR: Use 
appropriate colours when Spyder is in dark mode 
([133](https://github.com/spyder-ide/spyder-unittest/issues/133))
+* [PR 134](https://github.com/spyder-ide/spyder-unittest/pull/134) - PR: Allow 
for unittest tests to have docstrings 
([129](https://github.com/spyder-ide/spyder-unittest/issues/129))
+* [PR 132](https://github.com/spyder-ide/spyder-unittest/pull/132) - PR: Use 
nodeid provided by pytest in itemcollected hook 
([128](https://github.com/spyder-ide/spyder-unittest/issues/128))
+* [PR 131](https://github.com/spyder-ide/spyder-unittest/pull/131) - PR: 
Compatibility fixes for Spyder 4
+
+In this release 4 pull requests were closed.
+
+
 ## Version 0.3.1 (2018/06/15)
 
 This version fixes some bugs and also includes some cosmetic changes.
@@ -8,13 +30,13 @@
 
 * [Issue 117](https://github.com/spyder-ide/spyder-unittest/issues/117) - 
Rename "py.test" to "pytest" throughout ([PR 
119](https://github.com/spyder-ide/spyder-unittest/pull/119))
 * [Issue 113](https://github.com/spyder-ide/spyder-unittest/issues/113) - 
NameError in test file causes internal error ([PR 
118](https://github.com/spyder-ide/spyder-unittest/pull/118))
-* [Issue 112](https://github.com/spyder-ide/spyder-unittest/issues/112) - 
Plugin confused by tests writing to sys.__stdout__ ([PR 
114](https://github.com/spyder-ide/spyder-unittest/pull/114))
+* [Issue 112](https://github.com/spyder-ide/spyder-unittest/issues/112) - 
Plugin confused by tests writing to `sys.__stdout__` ([PR 
114](https://github.com/spyder-ide/spyder-unittest/pull/114))
 
 In this release 3 issues were closed.
 
 ### Pull Requests Merged
 
-* [PR 121](https://github.com/spyder-ide/spyder-unittest/pull/121) - PR: 
Update readme to remove funding appeal, harmonize with other readmes and minor 
fixes ([](a few obvious bugs/typos in the text. You can preview the full 
changes live over on [my 
repo](https://github.com/CAM-Gerlach/spyder-unittest/blob/update-readme/issues/))
+* [PR 121](https://github.com/spyder-ide/spyder-unittest/pull/121) - PR: 
Update readme to remove funding appeal, harmonize with other readmes and minor 
fixes
 * [PR 120](https://github.com/spyder-ide/spyder-unittest/pull/120) - Remove 
unused variables when initializing localization
 * [PR 119](https://github.com/spyder-ide/spyder-unittest/pull/119) - Replace 
'py.test' by 'pytest' 
([117](https://github.com/spyder-ide/spyder-unittest/issues/117))
 * [PR 118](https://github.com/spyder-ide/spyder-unittest/pull/118) - Use str() 
to convert pytest's longrepr to a string 
([113](https://github.com/spyder-ide/spyder-unittest/issues/113))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/spyder_unittest-0.3.1/PKG-INFO 
new/spyder_unittest-0.4.0/PKG-INFO
--- old/spyder_unittest-0.3.1/PKG-INFO  2018-06-15 23:12:31.000000000 +0200
+++ new/spyder_unittest-0.4.0/PKG-INFO  2020-01-07 15:30:55.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: spyder_unittest
-Version: 0.3.1
+Version: 0.4.0
 Summary: Plugin to run tests from within the Spyder IDE
 Home-page: https://github.com/spyder-ide/spyder-unittest
 Author: Spyder Project Contributors
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/spyder_unittest-0.3.1/README.md 
new/spyder_unittest-0.4.0/README.md
--- old/spyder_unittest-0.3.1/README.md 2018-06-15 23:07:26.000000000 +0200
+++ new/spyder_unittest-0.4.0/README.md 2020-01-02 15:38:07.000000000 +0100
@@ -1,5 +1,6 @@
 # Spyder-Unittest
 
+## Project information
 [![license](https://img.shields.io/pypi/l/spyder-unittest.svg)](./LICENSE)
 [![pypi 
version](https://img.shields.io/pypi/v/spyder-unittest.svg)](https://pypi.org/project/spyder-unittest/)
 [![conda 
version](https://img.shields.io/conda/v/spyder-ide/spyder-unittest.svg)](https://www.anaconda.com/download/)
@@ -7,16 +8,18 @@
 [![OpenCollective 
Backers](https://opencollective.com/spyder/backers/badge.svg?color=blue)](#backers)
 [![Join the chat at 
https://gitter.im/spyder-ide/public](https://badges.gitter.im/spyder-ide/spyder.svg)](https://gitter.im/spyder-ide/public)<br>
 [![PyPI 
status](https://img.shields.io/pypi/status/spyder-unittest.svg)](https://github.com/spyder-ide/spyder-unittest)
+
+## Build status
 [![Build 
Status](https://travis-ci.org/spyder-ide/spyder-unittest.svg?branch=master)](https://travis-ci.org/spyder-ide/spyder-unittest)
 [![Build 
status](https://ci.appveyor.com/api/projects/status/d9wa6whp1fpq4uii?svg=true)](https://ci.appveyor.com/project/spyder-ide/spyder-unittest)
 
[![CircleCI](https://circleci.com/gh/spyder-ide/spyder-unittest/tree/master.svg?style=shield)](https://circleci.com/gh/spyder-ide/spyder-unittest/tree/master)
 [![Coverage 
Status](https://coveralls.io/repos/github/spyder-ide/spyder-unittest/badge.svg?branch=master)](https://coveralls.io/github/spyder-ide/spyder-unittest?branch=master)
+[![Crowdin](https://badges.crowdin.net/spyder-unittest/localized.svg)](https://crowdin.com/project/spyder-unittest)
 
 *Copyright © 2014–2018 Spyder Project Contributors*
 
 ![Screenshot of spyder-unittest plugin showing test results](./screenshot.png)
 
-
 ## Description
 
 Spyder-unittest is a plugin that integrates popular unit test frameworks
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/spyder_unittest-0.3.1/spyder_unittest/__init__.py 
new/spyder_unittest-0.4.0/spyder_unittest/__init__.py
--- old/spyder_unittest-0.3.1/spyder_unittest/__init__.py       2018-06-15 
23:09:09.000000000 +0200
+++ new/spyder_unittest-0.4.0/spyder_unittest/__init__.py       2020-01-07 
13:42:28.000000000 +0100
@@ -8,5 +8,5 @@
 # Local imports
 from .unittestplugin import UnitTestPlugin as PLUGIN_CLASS
 
-__version__ = '0.3.1'
+__version__ = '0.4.0'
 PLUGIN_CLASS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/spyder_unittest-0.3.1/spyder_unittest/backend/pytestworker.py 
new/spyder_unittest-0.4.0/spyder_unittest/backend/pytestworker.py
--- old/spyder_unittest-0.3.1/spyder_unittest/backend/pytestworker.py   
2018-06-06 20:45:39.000000000 +0200
+++ new/spyder_unittest-0.4.0/spyder_unittest/backend/pytestworker.py   
2020-01-03 17:11:03.000000000 +0100
@@ -55,14 +55,9 @@
 
     def pytest_itemcollected(self, item):
         """Called by pytest when a test item is collected."""
-        nodeid = item.name
-        x = item.parent
-        while x.parent:
-            nodeid = x.name + '::' + nodeid
-            x = x.parent
         self.writer.write({
             'event': 'collected',
-            'nodeid': nodeid
+            'nodeid': item.nodeid
         })
 
     def pytest_runtest_logstart(self, nodeid, location):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/spyder_unittest-0.3.1/spyder_unittest/backend/tests/test_pytestworker.py 
new/spyder_unittest-0.4.0/spyder_unittest/backend/tests/test_pytestworker.py
--- 
old/spyder_unittest-0.3.1/spyder_unittest/backend/tests/test_pytestworker.py    
    2018-06-06 20:45:39.000000000 +0200
+++ 
new/spyder_unittest-0.4.0/spyder_unittest/backend/tests/test_pytestworker.py    
    2020-01-03 17:11:03.000000000 +0100
@@ -52,12 +52,7 @@
 
 def test_spyderplugin_test_itemcollected(plugin):
     testitem = EmptyClass()
-    testitem.name = 'bar'
-    testitem.parent = EmptyClass()
-    testitem.parent.name = 'foo.py'
-    testitem.parent.parent = EmptyClass
-    testitem.parent.parent.name = 'notused'
-    testitem.parent.parent.parent = None
+    testitem.nodeid = 'foo.py::bar'
     plugin.pytest_itemcollected(testitem)
     plugin.writer.write.assert_called_once_with({
         'event': 'collected',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/spyder_unittest-0.3.1/spyder_unittest/backend/tests/test_unittestrunner.py 
new/spyder_unittest-0.4.0/spyder_unittest/backend/tests/test_unittestrunner.py
--- 
old/spyder_unittest-0.3.1/spyder_unittest/backend/tests/test_unittestrunner.py  
    2018-01-27 12:17:11.000000000 +0100
+++ 
new/spyder_unittest-0.4.0/spyder_unittest/backend/tests/test_unittestrunner.py  
    2020-01-07 13:43:12.000000000 +0100
@@ -10,10 +10,15 @@
 from spyder_unittest.backend.unittestrunner import UnittestRunner
 
 
-def test_unittestrunner_load_data():
+def test_unittestrunner_load_data_with_two_tests():
     output = """test_isupper (teststringmethods.TestStringMethods) ... ok
 test_split (teststringmethods.TestStringMethods) ... ok
-extra text\n"""
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.012s
+
+OK
+"""
     runner = UnittestRunner(None)
     res = runner.load_data(output)
     assert len(res) == 2
@@ -28,10 +33,10 @@
     assert res[1].status == 'ok'
     assert res[1].name == 'teststringmethods.TestStringMethods.test_split'
     assert res[1].message == ''
-    assert res[1].extra_text == ['extra text\n']
+    assert res[1].extra_text == []
 
 
-def test_unittestrunner_load_data_removes_footer():
+def test_unittestrunner_load_data_with_one_test():
     output = """test1 (test_foo.Bar) ... ok
 
 ----------------------------------------------------------------------
@@ -59,6 +64,11 @@
   File "/somepath/test_foo.py", line 5, in test1
     self.assertEqual(1, 2)
 AssertionError: 1 != 2
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.012s
+
+FAILED (failures=1)
 """
     runner = UnittestRunner(None)
     res = runner.load_data(output)
@@ -68,7 +78,32 @@
     assert res[0].status == 'FAIL'
     assert res[0].name == 'test_foo.Bar.test1'
     assert res[0].extra_text[0].startswith('Traceback')
-    assert res[0].extra_text[-1].endswith('AssertionError: 1 != 2\n')
+    assert res[0].extra_text[-1].endswith('AssertionError: 1 != 2')
+
+    assert res[1].category == Category.OK
+    assert res[1].status == 'ok'
+    assert res[1].name == 'test_foo.Bar.test2'
+    assert res[1].extra_text == []
+
+
+def test_unittestrunner_load_data_with_comment():
+    output = """test1 (test_foo.Bar)
+comment ... ok
+test2 (test_foo.Bar) ... ok
+
+----------------------------------------------------------------------
+Ran 2 tests in 0.000s
+
+OK
+"""
+    runner = UnittestRunner(None)
+    res = runner.load_data(output)
+    assert len(res) == 2
+
+    assert res[0].category == Category.OK
+    assert res[0].status == 'ok'
+    assert res[0].name == 'test_foo.Bar.test1'
+    assert res[0].extra_text == []
 
     assert res[1].category == Category.OK
     assert res[1].status == 'ok'
@@ -76,30 +111,59 @@
     assert res[1].extra_text == []
 
 
+def test_unittestrunner_load_data_with_fail_and_comment():
+    output = """test1 (test_foo.Bar)
+comment ... FAIL
+
+======================================================================
+FAIL: test1 (test_foo.Bar)
+comment
+----------------------------------------------------------------------
+Traceback (most recent call last):
+  File "/somepath/test_foo.py", line 30, in test1
+    self.assertEqual(1, 2)
+AssertionError: 1 != 2
+
+----------------------------------------------------------------------
+Ran 1 test in 0.000s
+
+FAILED (failures=1)
+"""
+    runner = UnittestRunner(None)
+    res = runner.load_data(output)
+    assert len(res) == 1
+
+    assert res[0].category == Category.FAIL
+    assert res[0].status == 'FAIL'
+    assert res[0].name == 'test_foo.Bar.test1'
+    assert res[0].extra_text[0].startswith('Traceback')
+    assert res[0].extra_text[-1].endswith('AssertionError: 1 != 2')
+
+
 def test_try_parse_header_with_ok():
     runner = UnittestRunner(None)
-    text = 'test_isupper (testfoo.TestStringMethods) ... ok'
-    res = runner.try_parse_result(text)
-    assert res == ('test_isupper', 'testfoo.TestStringMethods', 'ok', '')
+    lines = ['test_isupper (testfoo.TestStringMethods) ... ok']
+    res = runner.try_parse_result(lines, 0)
+    assert res == (1, 'test_isupper', 'testfoo.TestStringMethods', 'ok', '')
 
 
 def test_try_parse_header_with_xfail():
     runner = UnittestRunner(None)
-    text = 'test_isupper (testfoo.TestStringMethods) ... expected failure'
-    res = runner.try_parse_result(text)
-    assert res == ('test_isupper', 'testfoo.TestStringMethods',
+    lines = ['test_isupper (testfoo.TestStringMethods) ... expected failure']
+    res = runner.try_parse_result(lines, 0)
+    assert res == (1, 'test_isupper', 'testfoo.TestStringMethods',
                    'expected failure', '')
 
 
 def test_try_parse_header_with_message():
     runner = UnittestRunner(None)
-    text = "test_nothing (testfoo.Tests) ... skipped 'msg'"
-    res = runner.try_parse_result(text)
-    assert res == ('test_nothing', 'testfoo.Tests', 'skipped', 'msg')
+    lines = ["test_nothing (testfoo.Tests) ... skipped 'msg'"]
+    res = runner.try_parse_result(lines, 0)
+    assert res == (1, 'test_nothing', 'testfoo.Tests', 'skipped', 'msg')
 
 
 def test_try_parse_header_starting_with_digit():
     runner = UnittestRunner(None)
-    text = '0est_isupper (testfoo.TestStringMethods) ... ok'
-    res = runner.try_parse_result(text)
+    lines = ['0est_isupper (testfoo.TestStringMethods) ... ok']
+    res = runner.try_parse_result(lines, 0)
     assert res is None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/spyder_unittest-0.3.1/spyder_unittest/backend/unittestrunner.py 
new/spyder_unittest-0.4.0/spyder_unittest/backend/unittestrunner.py
--- old/spyder_unittest-0.3.1/spyder_unittest/backend/unittestrunner.py 
2018-02-11 22:08:18.000000000 +0100
+++ new/spyder_unittest-0.4.0/spyder_unittest/backend/unittestrunner.py 
2020-01-07 13:43:12.000000000 +0100
@@ -36,6 +36,8 @@
         """
         Read and parse output from unittest module.
 
+        Any parsing errors are silently ignored.
+
         Returns
         -------
         list of TestResult
@@ -44,104 +46,96 @@
         res = []
         lines = output.splitlines()
         line_index = 0
-        test_index = None
 
-        while line_index < len(lines):
-            data = self.try_parse_result(lines[line_index])
+        while lines[line_index]:
+            data = self.try_parse_result(lines, line_index)
             if data:
-                if data[2] == 'ok':
+                line_index = data[0]
+                if data[3] == 'ok':
                     cat = Category.OK
-                elif data[2] == 'FAIL' or data[2] == 'ERROR':
+                elif data[3] == 'FAIL' or data[3] == 'ERROR':
                     cat = Category.FAIL
                 else:
                     cat = Category.SKIP
-                name = '{}.{}'.format(data[1], data[0])
-                tr = TestResult(category=cat, status=data[2], name=name,
-                                message=data[3])
+                name = '{}.{}'.format(data[2], data[1])
+                tr = TestResult(category=cat, status=data[3], name=name,
+                                message=data[4])
                 res.append(tr)
+            else:
                 line_index += 1
-                test_index = -1
-                continue
 
-            data = self.try_parse_exception_header(lines, line_index)
+        line_index += 1
+        while not (lines[line_index]
+                   and all(c == '-' for c in lines[line_index])):
+            data = self.try_parse_exception_block(lines, line_index)
             if data:
                 line_index = data[0]
                 test_index = next(
                     i for i, tr in enumerate(res)
                     if tr.name == '{}.{}'.format(data[2], data[1]))
-
-            data = self.try_parse_footer(lines, line_index)
-            if data:
-                line_index = data
-                test_index = -1
-                continue
-
-            if test_index is not None:
-                res[test_index].extra_text.append(lines[line_index] + '\n')
+                res[test_index].extra_text = data[3]
+            else:
                 line_index += 1
 
         return res
 
-    def try_parse_result(self, line):
+    def try_parse_result(self, lines, line_index):
         """
-        Try to parse a line of text as a test result.
+        Try to parse one or more lines of text as a test result.
 
         Returns
         -------
-        tuple of str or None
-            If line represents a test result, then return a tuple with four
-            strings: the name of the test function, the name of the test class,
-            the test result, and the reason (if no reason is given, the fourth
-            string is empty). Otherwise, return None.
-        """
-        regexp = (r'([^\d\W]\w*) \(([^\d\W][\w.]*)\) \.\.\. '
-                  '(ok|FAIL|ERROR|skipped|expected failure|unexpected success)'
-                  "( '([^']*)')?\Z")
-        match = re.match(regexp, line)
+        (int, str, str, str, str) or None
+            If a test result is parsed successfully then return a tuple with
+            the line index of the first line after the test result, the name
+            of the test function, the name of the test class, the test result,
+            and the reason (if no reason is given, the fourth string is empty).
+            Otherwise, return None.
+        """
+        regexp = r'([^\d\W]\w*) \(([^\d\W][\w.]*)\)'
+        match = re.match(regexp, lines[line_index])
         if match:
-            msg = match.groups()[4] or ''
-            return match.groups()[:3] + (msg, )
+            function_name = match.group(1)
+            class_name = match.group(2)
         else:
             return None
+        while lines[line_index]:
+            regexp = (r' \.\.\. (ok|FAIL|ERROR|skipped|expected failure|'
+                      r"unexpected success)( '([^']*)')?\Z")
+            match = re.search(regexp, lines[line_index])
+            if match:
+                result = match.group(1)
+                msg = match.group(3) or ''
+                return (line_index + 1, function_name, class_name, result, msg)
+            line_index += 1
+        return None
 
-    def try_parse_exception_header(self, lines, line_index):
+    def try_parse_exception_block(self, lines, line_index):
         """
-        Try to parse the header of an exception in unittest output.
+        Try to parse a block detailing an exception in unittest output.
 
         Returns
         -------
-        (int, str, str) or None
-            If an exception header is parsed successfully, then return a tuple
-            with the new line index, the name of the test function, and the
-            name of the test class. Otherwise, return None.
+        (int, str, str, list of str) or None
+            If an exception block is parsed successfully, then return a tuple
+            with the line index of the first line after the block, the name of
+            the test function, the name of the test class, and the text of the
+            exception. Otherwise, return None.
         """
-        if lines[line_index] != '':
-            return None
-        if not all(char == '=' for char in lines[line_index + 1]):
+        if not all(char == '=' for char in lines[line_index]):
             return None
         regexp = r'\w+: ([^\d\W]\w*) \(([^\d\W][\w.]*)\)\Z'
-        match = re.match(regexp, lines[line_index + 2])
+        match = re.match(regexp, lines[line_index + 1])
         if not match:
             return None
-        if not all(char == '-' for char in lines[line_index + 3]):
-            return None
-        return (line_index + 4, ) + match.groups()
-
-    def try_parse_footer(self, lines, line_index):
-        """
-        Try to parse footer of unittest output.
-
-        Returns
-        -------
-        int or None
-            New line index if footer is parsed successfully, None otherwise
-        """
-        if lines[line_index] != '':
-            return None
-        if not all(char == '-' for char in lines[line_index + 1]):
-            return None
-        if not re.match(r'^Ran [\d]+ tests? in', lines[line_index + 2]):
-            return None
-        if lines[line_index + 3] != '':
-            return None
-        return line_index + 5
+        line_index += 1
+        while not all(char == '-' for char in lines[line_index]):
+            if not lines[line_index]:
+                return None
+            line_index += 1
+        line_index += 1
+        exception_text = []
+        while lines[line_index]:
+            exception_text.append(lines[line_index])
+            line_index += 1
+        return (line_index, match.group(1), match.group(2), exception_text)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/spyder_unittest-0.3.1/spyder_unittest/tests/test_unittestplugin.py 
new/spyder_unittest-0.4.0/spyder_unittest/tests/test_unittestplugin.py
--- old/spyder_unittest-0.3.1/spyder_unittest/tests/test_unittestplugin.py      
2018-06-06 20:45:39.000000000 +0200
+++ new/spyder_unittest-0.4.0/spyder_unittest/tests/test_unittestplugin.py      
2020-01-02 15:38:07.000000000 +0100
@@ -7,6 +7,7 @@
 
 # Third party imports
 import pytest
+from spyder.plugins.projects.projecttypes import EmptyProject
 
 # Local imports
 from spyder_unittest.unittestplugin import UnitTestPlugin
@@ -18,10 +19,16 @@
     from mock import Mock  # Python 2
 
 
+class PluginForTesting(UnitTestPlugin):
+    CONF_FILE = False
+
+    def __init__(self, parent):
+        UnitTestPlugin.__init__(self, parent)
+
 @pytest.fixture
 def plugin(qtbot):
     """Set up the unittest plugin."""
-    res = UnitTestPlugin(None)
+    res = PluginForTesting(None)
     qtbot.addWidget(res)
     res.main = Mock()
     res.main.get_spyder_pythonpath = lambda: 'fakepythonpath'
@@ -70,9 +77,7 @@
     assert plugin.unittestwidget.default_wdir == 'fakecwd'
 
     # Test after opening project, default_wdir is set to project dir
-    project = Mock()
-    project.CONF = {}
-    project.root_path = str(tmpdir)
+    project = EmptyProject(str(tmpdir))
     plugin.main.projects.get_active_project = lambda: project
     plugin.main.projects.get_active_project_path = lambda: project.root_path
     plugin.handle_project_change()
@@ -87,14 +92,12 @@
 
 def test_plugin_config(plugin, tmpdir, qtbot):
     # Test config file does not exist and config is empty
-    config_file_path = tmpdir.join('.spyproject', 'unittest.ini')
+    config_file_path = tmpdir.join('.spyproject', 'config', 'unittest.ini')
     assert not config_file_path.check()
     assert plugin.unittestwidget.config is None
 
     # Open project
-    project = Mock()
-    project.CONF = {}
-    project.root_path = str(tmpdir)
+    project = EmptyProject(str(tmpdir))
     plugin.main.projects.get_active_project = lambda: project
     plugin.main.projects.get_active_project_path = lambda: project.root_path
     plugin.handle_project_change()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/spyder_unittest-0.3.1/spyder_unittest/unittestplugin.py 
new/spyder_unittest-0.4.0/spyder_unittest/unittestplugin.py
--- old/spyder_unittest-0.3.1/spyder_unittest/unittestplugin.py 2018-02-02 
23:12:29.000000000 +0100
+++ new/spyder_unittest-0.4.0/spyder_unittest/unittestplugin.py 2020-01-07 
13:43:12.000000000 +0100
@@ -5,14 +5,17 @@
 # (see LICENSE.txt for details)
 """Unit testing Plugin."""
 
+# Standard library imports
+import os.path as osp
+
 # Third party imports
 from qtpy.QtWidgets import QVBoxLayout
+from spyder.api.plugins import SpyderPluginWidget
 from spyder.config.base import get_translation
-from spyder.plugins import SpyderPluginWidget
+from spyder.config.gui import is_dark_interface
 from spyder.py3compat import getcwd
 from spyder.utils import icon_manager as ima
 from spyder.utils.qthelpers import create_action
-from spyder.widgets.projects.config import ProjectConfig
 
 # Local imports
 from spyder_unittest.widgets.configdialog import Config
@@ -26,6 +29,7 @@
 
     CONF_SECTION = 'unittest'
     CONF_DEFAULTS = [(CONF_SECTION, {'framework': '', 'wdir': ''})]
+    CONF_NAMEMAP = {CONF_SECTION: [(CONF_SECTION, ['framework', 'wdir'])]}
     CONF_VERSION = '0.1.0'
 
     def __init__(self, parent):
@@ -36,29 +40,16 @@
         `self.register_plugin()`.
         """
         SpyderPluginWidget.__init__(self, parent)
-        self.main = parent  # Spyder 3 compatibility
-
-        # Create unit test widget. For compatibility with Spyder 3.x
-        # here we check if the plugin has the attributes
-        # 'options_button' and 'options_menu'. See issue 83
-        if hasattr(self, 'options_button') and hasattr(self, 'options_menu'):
-            # Works with Spyder 4.x
-            self.unittestwidget = UnitTestWidget(
-                self.main,
-                options_button=self.options_button,
-                options_menu=self.options_menu)
-        else:
-            # Works with Spyder 3.x
-            self.unittestwidget = UnitTestWidget(self.main)
 
-        # Add unit test widget in dockwindow
+        # Create unit test widget and add to dockwindow
+        self.unittestwidget = UnitTestWidget(
+            self.main,
+            options_button=self.options_button,
+            options_menu=self._options_menu)
         layout = QVBoxLayout()
         layout.addWidget(self.unittestwidget)
         self.setLayout(layout)
 
-        # Initialize plugin
-        self.initialize_plugin()
-
     def update_pythonpath(self):
         """
         Update Python path used to run unit tests.
@@ -101,27 +92,33 @@
         then use it. If it is not valid (e.g., because the user never
         configured testing for this project) or no project is opened, then
         invalidate the current test configuration.
+
+        If necessary, patch the project preferences to include this plugin's
+        config options.
         """
         project = self.main.projects.get_active_project()
         if not project:
             self.unittestwidget.set_config_without_emit(None)
             return
 
-        try:
-            project_conf = project.CONF[self.CONF_SECTION]
-        except KeyError:
-            project_conf = ProjectConfig(
+        if self.CONF_SECTION not in project.config._name_map:
+            project.config._name_map = project.config._name_map.copy()
+            project.config._name_map.update(self.CONF_NAMEMAP)
+
+        if self.CONF_SECTION not in project.config._configs_map:
+            config_class = project.config.get_config_class()
+            path = osp.join(project.root_path, '.spyproject', 'config')
+            conf = config_class(
                 name=self.CONF_SECTION,
-                root_path=project.root_path,
-                filename=self.CONF_SECTION + '.ini',
                 defaults=self.CONF_DEFAULTS,
+                path=path,
                 load=True,
                 version=self.CONF_VERSION)
-            project.CONF[self.CONF_SECTION] = project_conf
+            project.config._configs_map[self.CONF_SECTION] = conf
 
         new_config = Config(
-            framework=project_conf.get(self.CONF_SECTION, 'framework'),
-            wdir=project_conf.get(self.CONF_SECTION, 'wdir'))
+            framework=project.get_option(self.CONF_SECTION, 'framework'),
+            wdir=project.get_option(self.CONF_SECTION, 'wdir'))
         if not self.unittestwidget.config_is_valid(new_config):
             new_config = None
         self.unittestwidget.set_config_without_emit(new_config)
@@ -135,9 +132,9 @@
         project = self.main.projects.get_active_project()
         if not project:
             return
-        project_conf = project.CONF[self.CONF_SECTION]
-        project_conf.set(self.CONF_SECTION, 'framework', test_config.framework)
-        project_conf.set(self.CONF_SECTION, 'wdir', test_config.wdir)
+        project.set_option(self.CONF_SECTION, 'framework',
+                           test_config.framework)
+        project.set_option(self.CONF_SECTION, 'wdir', test_config.wdir)
 
     def goto_in_editor(self, filename, lineno):
         """
@@ -174,9 +171,12 @@
 
     def register_plugin(self):
         """Register plugin in Spyder's main window."""
+        super(UnitTestPlugin, self).register_plugin()
+
         # Get information from Spyder proper into plugin
         self.update_pythonpath()
         self.update_default_wdir()
+        self.unittestwidget.use_dark_interface(is_dark_interface())
 
         # Connect to relevant signals
         self.main.sig_pythonpath_changed.connect(self.update_pythonpath)
@@ -191,9 +191,6 @@
         self.unittestwidget.sig_newconfig.connect(self.save_config)
         self.unittestwidget.sig_edit_goto.connect(self.goto_in_editor)
 
-        # Add plugin as dockwidget to main window
-        self.main.add_dockwidget(self)
-
         # Create action and add it to Spyder's menu
         unittesting_act = create_action(
             self,
@@ -209,11 +206,8 @@
 
     def refresh_plugin(self):
         """Refresh unit testing widget."""
-        # For compatibility with Spyder 3.x here we check if the plugin
-        # has the attributes 'options_button' and 'options_menu'. See issue 83
-        if hasattr(self, 'options_button') and hasattr(self, 'options_menu'):
-            self.options_menu.clear()
-            self.get_plugin_actions()
+        self._options_menu.clear()
+        self.get_plugin_actions()
 
     def closing_plugin(self, cancelable=False):
         """Perform actions before parent main window is closed."""
@@ -232,8 +226,6 @@
         not valid (or not set), then ask the user to configure. Then
         run the tests.
         """
-        if self.dockwidget and not self.ismaximized:
-            self.dockwidget.setVisible(True)
-            self.dockwidget.setFocus()
-            self.dockwidget.raise_()
+        if self.dockwidget:
+            self.switch_to_plugin()
         self.unittestwidget.maybe_configure_and_start()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/spyder_unittest-0.3.1/spyder_unittest/widgets/datatree.py 
new/spyder_unittest-0.4.0/spyder_unittest/widgets/datatree.py
--- old/spyder_unittest-0.3.1/spyder_unittest/widgets/datatree.py       
2018-06-15 23:07:26.000000000 +0200
+++ new/spyder_unittest-0.4.0/spyder_unittest/widgets/datatree.py       
2020-01-07 13:43:12.000000000 +0100
@@ -34,6 +34,13 @@
     Category.PENDING: QBrush(QColor("#C5C5C5"))
 }
 
+COLORS_DARK = {
+    Category.OK: QBrush(QColor("#008000")),
+    Category.FAIL: QBrush(QColor("#C6001E")),
+    Category.SKIP: QBrush(QColor("#505050")),
+    Category.PENDING: QBrush(QColor("#505050"))
+}
+
 STATUS_COLUMN = 0
 NAME_COLUMN = 1
 MESSAGE_COLUMN = 2
@@ -188,6 +195,11 @@
     a tuple (row, column, id). The id is TOPLEVEL_ID for top-level items.
     For level-2 items, the id is the index of the test in `self.testresults`.
 
+    Attributes
+    ----------
+    is_dark_interface : bool
+        Whether to use colours appropriate for a dark user interface.
+
     Signals
     -------
     sig_summary(str)
@@ -200,6 +212,7 @@
         """Constructor."""
         QAbstractItemModel.__init__(self, parent)
         self.abbreviator = Abbreviator()
+        self.is_dark_interface = False
         self.testresults = []
         try:
             self.monospace_font = parent.window().editor.get_plugin_font()
@@ -319,7 +332,10 @@
         elif role == Qt.BackgroundRole:
             if id == TOPLEVEL_ID:
                 testresult = self.testresults[row]
-                return COLORS[testresult.category]
+                if self.is_dark_interface:
+                    return COLORS_DARK[testresult.category]
+                else:
+                    return COLORS[testresult.category]
         elif role == Qt.TextAlignmentRole:
             if id == TOPLEVEL_ID and column == TIME_COLUMN:
                 return Qt.AlignRight
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/spyder_unittest-0.3.1/spyder_unittest/widgets/tests/test_datatree.py 
new/spyder_unittest-0.4.0/spyder_unittest/widgets/tests/test_datatree.py
--- old/spyder_unittest-0.3.1/spyder_unittest/widgets/tests/test_datatree.py    
2018-02-17 17:01:21.000000000 +0100
+++ new/spyder_unittest-0.4.0/spyder_unittest/widgets/tests/test_datatree.py    
2020-01-07 13:43:12.000000000 +0100
@@ -12,8 +12,8 @@
 
 # Local imports
 from spyder_unittest.backend.runnerbase import Category, TestResult
-from spyder_unittest.widgets.datatree import (COLORS, TestDataModel,
-                                              TestDataView)
+from spyder_unittest.widgets.datatree import (COLORS, COLORS_DARK,
+                                              TestDataModel, TestDataView)
 
 try:
     from unittest.mock import Mock
@@ -156,15 +156,19 @@
     model.testresults = [res]
     assert model.data(model.index(0, 3), Qt.DisplayRole) == ''
 
-def test_testdatamodel_data_background():
[email protected]('dark', [False, True])
+def test_testdatamodel_data_background(dark):
     model = TestDataModel()
+    if dark:
+        model.is_dark_interface = True
     res = [TestResult(Category.OK, 'status', 'foo.bar'),
            TestResult(Category.FAIL, 'error', 'foo.bar', 'kadoom')]
     model.testresults = res
     index = model.index(0, 0)
-    assert model.data(index, Qt.BackgroundRole) == COLORS[Category.OK]
+    colors = COLORS_DARK if dark else COLORS
+    assert model.data(index, Qt.BackgroundRole) == colors[Category.OK]
     index = model.index(1, 2)
-    assert model.data(index, Qt.BackgroundRole) == COLORS[Category.FAIL]
+    assert model.data(index, Qt.BackgroundRole) == colors[Category.FAIL]
 
 def test_testdatamodel_data_userrole():
     model = TestDataModel()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/spyder_unittest-0.3.1/spyder_unittest/widgets/unittestgui.py 
new/spyder_unittest-0.4.0/spyder_unittest/widgets/unittestgui.py
--- old/spyder_unittest-0.3.1/spyder_unittest/widgets/unittestgui.py    
2018-06-15 23:07:26.000000000 +0200
+++ new/spyder_unittest-0.4.0/spyder_unittest/widgets/unittestgui.py    
2020-01-07 13:43:12.000000000 +0100
@@ -19,7 +19,7 @@
 from spyder.config.base import get_conf_path, get_translation
 from spyder.utils import icon_manager as ima
 from spyder.utils.qthelpers import create_action, create_toolbutton
-from spyder.widgets.variableexplorer.texteditor import TextEditor
+from spyder.plugins.variableexplorer.widgets.texteditor import TextEditor
 
 # Local imports
 from spyder_unittest.backend.frameworkregistry import FrameworkRegistry
@@ -88,6 +88,7 @@
         self.default_wdir = None
         self.pre_test_hook = None
         self.testrunner = None
+
         self.output = None
         self.testdataview = TestDataView(self)
         self.testdatamodel = TestDataModel(self)
@@ -146,6 +147,10 @@
         """Set test configuration but do not emit any signal."""
         self._config = new_config
 
+    def use_dark_interface(self, flag):
+        """Set whether widget should use colours appropriate for dark UI."""
+        self.testdatamodel.is_dark_interface = flag
+
     def create_actions(self):
         """Create the actions for the unittest widget."""
         self.config_action = create_action(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/spyder_unittest-0.3.1/spyder_unittest.egg-info/PKG-INFO 
new/spyder_unittest-0.4.0/spyder_unittest.egg-info/PKG-INFO
--- old/spyder_unittest-0.3.1/spyder_unittest.egg-info/PKG-INFO 2018-06-15 
23:12:31.000000000 +0200
+++ new/spyder_unittest-0.4.0/spyder_unittest.egg-info/PKG-INFO 2020-01-07 
15:30:54.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: spyder-unittest
-Version: 0.3.1
+Version: 0.4.0
 Summary: Plugin to run tests from within the Spyder IDE
 Home-page: https://github.com/spyder-ide/spyder-unittest
 Author: Spyder Project Contributors


Reply via email to