Repository: spyder-ide/spyder

Issue:
test_mainwindow incorrectly picks up pytest arguments
While[ preparing the next spyder3 package for OpenSUSE Tumbleweed](https://build.opensuse.org/package/show/home:bnavigator:branches:devel:languages:python:numeric/spyder3), v4.1.1, I run into the following unittest fail, which has not happened before:

`test_mainwindow.py`  incorrectly checks the command line parameters for pytest as spyder parameters and fails with "invalid parameter"

OpenSUSE TW currently is upgrading a lot of packages while transitioning into Python3.8, so this might not be directly related to the spyder code. Any hints appreciated.

```
...
[  105s] + py.test-3.8 --ignore=_build.python2 --ignore=_build.python3 --ignore=_build.pypy3 -v -k 'not (test_github_backend or test_update or test_introspection or test_arrayeditor_edit_complex_array or test_mpl_backend_change or test_connection_dialog_remembers_input_with_ssh_passphrase or test_connection_dialog_remembers_input_with_password or test_dbg_input)' --ignore=spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py --ignore=spyder/plugins/ipythonconsole/comms/tests/test_comms.py
[  106s] ============================= test session starts ==============================
[  106s] platform linux -- Python 3.8.2, pytest-4.6.9, py-1.8.1, pluggy-0.13.1 -- /usr/bin/python3
[  106s] cachedir: .pytest_cache
[  106s] PyQt5 5.13.2 -- Qt runtime 5.14.1 -- Qt compiled 5.14.1
[  106s] rootdir: /home/abuild/rpmbuild/BUILD/spyder-4.1.1, inifile: pytest.ini
[  106s] plugins: qt-3.3.0, flaky-3.6.1, lazy-fixture-0.6.3, timeout-1.3.4, cov-2.8.1, xvfb-1.2.0, mock-2.0.0
[  126s] collecting ... collected 968 items / 1 errors / 33 deselected / 934 selected
[  126s] 
[  126s] ==================================== ERRORS ====================================
[  126s] _____________ ERROR collecting spyder/app/tests/test_mainwindow.py _____________
[  126s] spyder/app/tests/test_mainwindow.py:48: in <module>
[  126s]     from spyder.app.mainwindow import MainWindow  # Tests fail without this import
[  126s] spyder/app/mainwindow.py:108: in <module>
[  126s]     CLI_OPTIONS, CLI_ARGS = get_options()
[  126s] spyder/app/cli_options.py:133: in get_options
[  126s]     options = parser.parse_args(argv)
[  126s] /usr/lib64/python3.8/argparse.py:1771: in parse_args
[  126s]     self.error(msg % ' '.join(argv))
[  126s] /usr/lib64/python3.8/argparse.py:2521: in error
[  126s]     self.exit(2, _('%(prog)s: error: %(message)s\n') % args)
[  126s] /usr/lib64/python3.8/argparse.py:2508: in exit
[  126s]     _sys.exit(status)
[  126s] E   SystemExit: 2
[  126s] ------------------------------- Captured stderr --------------------------------
[  126s] usage: spyder [options] files
[  126s] py.test-3.8: error: unrecognized arguments: --ignore=_build.python2 --ignore=_build.python3 --ignore=_build.pypy3 -v -k --ignore=spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py --ignore=spyder/plugins/ipythonconsole/comms/tests/test_comms.py
...
```


Discussion:

Resolving patch:
diff --git a/runtests.py b/runtests.py
--- a/runtests.py
+++ b/runtests.py
@@ -9,18 +9,18 @@
 """
 
 # Standard library imports
-import os
-import sys
 import argparse
+import os
 
-# To activate/deactivate certain things for pytest's only
+# To activate/deactivate certain things for pytests only
 # NOTE: Please leave this before any other import here!!
 os.environ['SPYDER_PYTEST'] = 'True'
 
 # Third party imports
 # NOTE: This needs to be imported before any QApplication.
 # Don't remove it or change it to a different location!
-from qtpy import QtWebEngineWidgets
+# pylint: disable=wrong-import-position
+from qtpy import QtWebEngineWidgets  # pylint: disable=unused-import
 import pytest
 
 
@@ -29,30 +29,24 @@
 RUN_SLOW = os.environ.get('RUN_SLOW', None) == 'true'
 
 
-def main(run_slow=False, extra_args=None):
-    """
-    Run pytest tests for Spyder.
-    """
-    pytest_args = ['spyder',
-                   '-vv',
-                   '-rw',
-                   '--durations=10']
+def run_pytest(run_slow=False, extra_args=None):
+    """Run pytest tests for Spyder."""
+    pytest_args = ['-vv', '-rw', '--durations=10']
 
     if CI:
         # Exit on first failure and show coverage
         pytest_args += ['-x', '--cov=spyder', '--no-cov-on-fail']
 
-        # Run slow tests only
-        if RUN_SLOW:
-            pytest_args += ['--run-slow']
-
         # To display nice tests resume in Azure's web page
         if os.environ.get('AZURE', None) is not None:
             pytest_args += ['--cache-clear', '--junitxml=result.xml']
-    elif run_slow:
+    if run_slow or RUN_SLOW:
         pytest_args += ['--run-slow']
-    elif extra_args:
+    # Allow user to pass a custom test path to pytest to e.g. run just one test
+    if extra_args:
         pytest_args += extra_args
+    else:
+        pytest_args += ['spyder']
 
     print("Pytest Arguments: " + str(pytest_args))
     errno = pytest.main(pytest_args)
@@ -64,13 +58,16 @@ def main(run_slow=False, extra_args=None):
         raise SystemExit(errno)
 
 
-if __name__ == '__main__':
+def main():
+    """Parse args then run the pytest suite for Spyder."""
     test_parser = argparse.ArgumentParser(
-        usage='python runtests.py [--run-slow] [-- pytest_args]')
-    test_parser.add_argument('--run-slow', action='store_true',
-                             default=False,
+        usage='python runtests.py [-h] [--run-slow] [pytest_args]',
+        description="Helper script to run Spyder's test suite")
+    test_parser.add_argument('--run-slow', action='store_true', default=False,
                              help='Run the slow tests')
-    test_parser.add_argument('pytest_args', nargs='*',
-                             help="Args to pass to pytest")
-    test_args = test_parser.parse_args()
-    main(run_slow=test_args.run_slow, extra_args=test_args.pytest_args)
+    test_args, pytest_args = test_parser.parse_known_args()
+    run_pytest(run_slow=test_args.run_slow, extra_args=pytest_args)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/spyder/app/mainwindow.py b/spyder/app/mainwindow.py
--- a/spyder/app/mainwindow.py
+++ b/spyder/app/mainwindow.py
@@ -103,9 +103,15 @@
 #==============================================================================
 from spyder.app.utils import set_opengl_implementation
 from spyder.app.cli_options import get_options
+from spyder.config.base import running_under_pytest
 
-# Get CLI options/args and make them available for future use
-CLI_OPTIONS, CLI_ARGS = get_options()
+# Get CLI options/args and make them available for future use.
+# Ignore args if running tests or Spyder will try and fail to parse pytests's.
+if running_under_pytest():
+    sys_argv = [sys.argv[0]]
+else:
+    sys_argv = sys.argv
+CLI_OPTIONS, CLI_ARGS = get_options(sys_argv)
 
 # **** Set OpenGL implementation to use ****
 if CLI_OPTIONS.opengl_implementation:
@@ -134,7 +140,7 @@
 #==============================================================================
 # Create splash screen out of MainWindow to reduce perceived startup time.
 #==============================================================================
-from spyder.config.base import _, get_image_path, DEV, running_under_pytest
+from spyder.config.base import _, get_image_path, DEV
 
 if not running_under_pytest():
     SPLASH = QSplashScreen(QPixmap(get_image_path('splash.svg')))
@@ -3664,32 +3670,13 @@ def run_spyder(app, options, args):
 def main():
     """Main function"""
     # **** For Pytest ****
-    # We need to create MainWindow **here** to avoid passing pytest
-    # options to Spyder
     if running_under_pytest():
-        try:
-            from unittest.mock import Mock
-        except ImportError:
-            from mock import Mock  # Python 2
-
-        options = Mock()
-        options.working_directory = None
-        options.profile = False
-        options.multithreaded = False
-        options.new_instance = False
-        options.project = None
-        options.window_title = None
-        options.opengl_implementation = None
-        options.debug_info = None
-        options.debug_output = None
-        options.paths = None
-
         if CONF.get('main', 'opengl') != 'automatic':
             option = CONF.get('main', 'opengl')
             set_opengl_implementation(option)
 
         app = initialize()
-        window = run_spyder(app, options, None)
+        window = run_spyder(app, CLI_OPTIONS, None)
         return window
 
     # **** Collect command line options ****


Test patch:
diff --git a/spyder/app/tests/test_mainwindow.py b/spyder/app/tests/test_mainwindow.py
--- a/spyder/app/tests/test_mainwindow.py
+++ b/spyder/app/tests/test_mainwindow.py
@@ -546,8 +546,9 @@ def test_get_help_ipython_console(main_window, qtbot):
 
 @pytest.mark.slow
 @flaky(max_runs=3)
-@pytest.mark.skipif(not sys.platform.startswith('linux'),
-                    reason="Only works on Linux")
+@pytest.mark.skipif((not sys.platform.startswith('linux') or
+                     os.environ.get('CI', None) is None),
+                    reason="Only works on Linux and CIs")
 @pytest.mark.use_introspection
 @pytest.mark.parametrize(
     "object_info",