1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Copyright © Spyder Project Contributors
4#
5# Licensed under the terms of the MIT License
6# (see spyder/__init__.py for details)
7# -----------------------------------------------------------------------------
8
9"""
10Tests for the main window
11"""
12
13# Standard library imports
14import os
15import os.path as osp
16import shutil
17import tempfile
18try:
19    from unittest.mock import Mock, MagicMock
20except ImportError:
21    from mock import Mock, MagicMock  # Python 2
22
23# Third party imports
24from flaky import flaky
25from jupyter_client.manager import KernelManager
26import numpy as np
27from numpy.testing import assert_array_equal
28import pytest
29from qtpy import PYQT4, PYQT5, PYQT_VERSION
30from qtpy.QtCore import Qt, QTimer, QEvent, QUrl
31from qtpy.QtTest import QTest
32from qtpy.QtWidgets import QApplication, QFileDialog, QLineEdit, QTabBar
33
34# Local imports
35from spyder import __trouble_url__, __project_url__
36from spyder.app import start
37from spyder.app.mainwindow import MainWindow  # Tests fail without this import
38from spyder.config.base import get_home_dir
39from spyder.config.main import CONF
40from spyder.plugins import TabFilter
41from spyder.plugins.help import ObjectComboBox
42from spyder.plugins.runconfig import RunConfiguration
43from spyder.py3compat import PY2, to_text_string
44from spyder.utils.ipython.kernelspec import SpyderKernelSpec
45from spyder.utils.programs import is_module_installed
46
47# For testing various Spyder urls
48if not PY2:
49    from urllib.request import urlopen
50    from urllib.error import URLError
51else:
52    from urllib2 import urlopen, URLError
53
54
55# =============================================================================
56# Constants
57# =============================================================================
58# Location of this file
59LOCATION = osp.realpath(osp.join(os.getcwd(), osp.dirname(__file__)))
60
61# Time to wait until the IPython console is ready to receive input
62# (in miliseconds)
63SHELL_TIMEOUT = 20000
64
65# Need longer EVAL_TIMEOUT, because need to cythonize and C compile ".pyx" file
66# before import and eval it
67COMPILE_AND_EVAL_TIMEOUT = 30000
68
69# Time to wait for the IPython console to evaluate something (in
70# miliseconds)
71EVAL_TIMEOUT = 3000
72
73# Temporary directory
74TEMP_DIRECTORY = tempfile.gettempdir()
75
76
77# =============================================================================
78# Utility functions
79# =============================================================================
80def open_file_in_editor(main_window, fname, directory=None):
81    """Open a file using the Editor and its open file dialog"""
82    top_level_widgets = QApplication.topLevelWidgets()
83    for w in top_level_widgets:
84        if isinstance(w, QFileDialog):
85            if directory is not None:
86                w.setDirectory(directory)
87            input_field = w.findChildren(QLineEdit)[0]
88            input_field.setText(fname)
89            QTest.keyClick(w, Qt.Key_Enter)
90
91
92def get_thirdparty_plugin(main_window, plugin_title):
93    """Get a reference to the thirdparty plugin with the title given."""
94    for plugin in main_window.thirdparty_plugins:
95        if plugin.get_plugin_title() == plugin_title:
96            return plugin
97
98
99def reset_run_code(qtbot, shell, code_editor, nsb):
100    """Reset state after a run code test"""
101    with qtbot.waitSignal(shell.executed):
102        shell.execute('%reset -f')
103    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 0, timeout=EVAL_TIMEOUT)
104    code_editor.setFocus()
105    qtbot.keyClick(code_editor, Qt.Key_Home, modifier=Qt.ControlModifier)
106
107
108def start_new_kernel(startup_timeout=60, kernel_name='python', spykernel=False,
109                     **kwargs):
110    """Start a new kernel, and return its Manager and Client"""
111    km = KernelManager(kernel_name=kernel_name)
112    if spykernel:
113        km._kernel_spec = SpyderKernelSpec()
114    km.start_kernel(**kwargs)
115    kc = km.client()
116    kc.start_channels()
117    try:
118        kc.wait_for_ready(timeout=startup_timeout)
119    except RuntimeError:
120        kc.stop_channels()
121        km.shutdown_kernel()
122        raise
123
124    return km, kc
125
126
127def find_desired_tab_in_window(tab_name, window):
128    all_tabbars = window.findChildren(QTabBar)
129    for current_tabbar in all_tabbars:
130        for tab_index in range(current_tabbar.count()):
131            if current_tabbar.tabText(tab_index) == str(tab_name):
132                return current_tabbar, tab_index
133    return None, None
134
135
136# =============================================================================
137# Fixtures
138# =============================================================================
139@pytest.fixture
140def main_window(request):
141    """Main Window fixture"""
142    # Tests assume inline backend
143    CONF.set('ipython_console', 'pylab/backend', 0)
144
145    # Check if we need to use introspection in a given test
146    # (it's faster and less memory consuming not to use it!)
147    use_introspection = request.node.get_marker('use_introspection')
148    if use_introspection:
149        os.environ['SPY_TEST_USE_INTROSPECTION'] = 'True'
150    else:
151        try:
152            os.environ.pop('SPY_TEST_USE_INTROSPECTION')
153        except KeyError:
154            pass
155
156    # Only use single_instance mode for tests that require it
157    single_instance = request.node.get_marker('single_instance')
158    if single_instance:
159        CONF.set('main', 'single_instance', True)
160    else:
161        CONF.set('main', 'single_instance', False)
162
163    # Start the window
164    window = start.main()
165
166    # Teardown
167    def close_window():
168        window.close()
169    request.addfinalizer(close_window)
170
171    return window
172
173
174# =============================================================================
175# Tests
176# =============================================================================
177# IMPORTANT NOTE: Please leave this test to be the first one here to
178# avoid possible timeouts in Appveyor
179@pytest.mark.slow
180@pytest.mark.use_introspection
181@flaky(max_runs=3)
182@pytest.mark.skipif(os.name == 'nt' or not PY2,
183                    reason="Times out on AppVeyor and fails on PY3/PyQt 5.6")
184@pytest.mark.timeout(timeout=45, method='thread')
185def test_calltip(main_window, qtbot):
186    """Test that the calltip in editor is hidden when matching ')' is found."""
187    # Load test file
188    text = 'a = [1,2,3]\n(max'
189    main_window.editor.new(fname="test.py", text=text)
190    code_editor = main_window.editor.get_focus_widget()
191
192    # Set text to start
193    code_editor.set_text(text)
194    code_editor.go_to_line(2)
195    code_editor.move_cursor(5)
196    calltip = code_editor.calltip_widget
197    assert not calltip.isVisible()
198
199    qtbot.keyPress(code_editor, Qt.Key_ParenLeft, delay=3000)
200    qtbot.keyPress(code_editor, Qt.Key_A, delay=1000)
201    qtbot.waitUntil(lambda: calltip.isVisible(), timeout=3000)
202
203    qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000)
204    qtbot.keyPress(code_editor, Qt.Key_Space)
205    assert not calltip.isVisible()
206    qtbot.keyPress(code_editor, Qt.Key_ParenRight, delay=1000)
207    qtbot.keyPress(code_editor, Qt.Key_Enter, delay=1000)
208
209    main_window.editor.close_file()
210
211
212@pytest.mark.slow
213def test_window_title(main_window, tmpdir):
214    """Test window title with non-ascii characters."""
215    projects = main_window.projects
216
217    # Create a project in non-ascii path
218    path = to_text_string(tmpdir.mkdir(u'測試'))
219    projects.open_project(path=path)
220
221    # Set non-ascii window title
222    main_window.window_title = u'اختبار'
223
224    # Assert window title is computed without errors
225    # and has the expected strings
226    main_window.set_window_title()
227    title = main_window.base_title
228    assert u'Spyder' in title
229    assert u'Python' in title
230    assert u'اختبار' in title
231    assert u'測試' in title
232
233    projects.close_project()
234
235
236@pytest.mark.slow
237@pytest.mark.single_instance
238def test_single_instance_and_edit_magic(main_window, qtbot, tmpdir):
239    """Test single instance mode and for %edit magic."""
240    editorstack = main_window.editor.get_current_editorstack()
241    shell = main_window.ipyconsole.get_current_shellwidget()
242    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
243
244    lock_code = ("from spyder.config.base import get_conf_path\n"
245                 "from spyder.utils.external import lockfile\n"
246                 "lock_file = get_conf_path('spyder.lock')\n"
247                 "lock = lockfile.FilesystemLock(lock_file)\n"
248                 "lock_created = lock.lock()")
249
250    # Test single instance
251    with qtbot.waitSignal(shell.executed):
252        shell.execute(lock_code)
253    assert not shell.get_value('lock_created')
254
255    # Test %edit magic
256    n_editors = editorstack.get_stack_count()
257    p = tmpdir.mkdir("foo").join("bar.py")
258    p.write(lock_code)
259
260    with qtbot.waitSignal(shell.executed):
261        shell.execute('%edit {}'.format(to_text_string(p)))
262
263    qtbot.wait(3000)
264    assert editorstack.get_stack_count() == n_editors + 1
265    assert editorstack.get_current_editor().toPlainText() == lock_code
266
267    main_window.editor.close_file()
268
269
270@pytest.mark.slow
271@flaky(max_runs=3)
272@pytest.mark.skipif(os.name == 'nt' or PY2 or PYQT4,
273                    reason="It fails sometimes")
274def test_move_to_first_breakpoint(main_window, qtbot):
275    """Test that we move to the first breakpoint if there's one present."""
276    # Wait until the window is fully up
277    shell = main_window.ipyconsole.get_current_shellwidget()
278    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
279
280    # Main variables
281    control = shell._control
282    debug_action = main_window.debug_toolbar_actions[0]
283    debug_button = main_window.debug_toolbar.widgetForAction(debug_action)
284
285    # Clear all breakpoints
286    main_window.editor.clear_all_breakpoints()
287
288    # Load test file
289    test_file = osp.join(LOCATION, 'script.py')
290    main_window.editor.load(test_file)
291    code_editor = main_window.editor.get_focus_widget()
292
293    # Set breakpoint
294    code_editor.add_remove_breakpoint(line_number=10)
295    qtbot.wait(500)
296
297    # Click the debug button
298    qtbot.mouseClick(debug_button, Qt.LeftButton)
299    qtbot.wait(1000)
300
301    # Verify that we are at first breakpoint
302    shell.clear_console()
303    qtbot.wait(500)
304    shell.kernel_client.input("list")
305    qtbot.wait(500)
306    assert "1--> 10 arr = np.array(li)" in control.toPlainText()
307
308    # Exit debugging
309    shell.kernel_client.input("exit")
310    qtbot.wait(500)
311
312    # Set breakpoint on first line with code
313    code_editor.add_remove_breakpoint(line_number=2)
314    qtbot.wait(500)
315
316    # Click the debug button
317    qtbot.mouseClick(debug_button, Qt.LeftButton)
318    qtbot.wait(1000)
319
320    # Verify that we are still on debugging
321    assert shell._reading
322
323    # Remove breakpoint and close test file
324    main_window.editor.clear_all_breakpoints()
325    main_window.editor.close_file()
326
327
328@pytest.mark.slow
329@flaky(max_runs=3)
330@pytest.mark.skipif(os.environ.get('CI', None) is None,
331                    reason="It's not meant to be run locally")
332def test_runconfig_workdir(main_window, qtbot, tmpdir):
333    """Test runconfig workdir options."""
334    CONF.set('run', 'configurations', [])
335
336    # ---- Load test file ----
337    test_file = osp.join(LOCATION, 'script.py')
338    main_window.editor.load(test_file)
339    code_editor = main_window.editor.get_focus_widget()
340
341    # --- Use cwd for this file ---
342    rc = RunConfiguration().get()
343    rc['file_dir'] = False
344    rc['cw_dir'] = True
345    config_entry = (test_file, rc)
346    CONF.set('run', 'configurations', [config_entry])
347
348    # --- Run test file ---
349    shell = main_window.ipyconsole.get_current_shellwidget()
350    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
351    qtbot.keyClick(code_editor, Qt.Key_F5)
352    qtbot.wait(500)
353
354    # --- Assert we're in cwd after execution ---
355    with qtbot.waitSignal(shell.executed):
356        shell.execute('import os; current_dir = os.getcwd()')
357    assert shell.get_value('current_dir') == get_home_dir()
358
359    # --- Use fixed execution dir for test file ---
360    temp_dir = str(tmpdir.mkdir("test_dir"))
361    rc['file_dir'] = False
362    rc['cw_dir'] = False
363    rc['fixed_dir'] = True
364    rc['dir'] = temp_dir
365    config_entry = (test_file, rc)
366    CONF.set('run', 'configurations', [config_entry])
367
368    # --- Run test file ---
369    shell = main_window.ipyconsole.get_current_shellwidget()
370    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
371    qtbot.keyClick(code_editor, Qt.Key_F5)
372    qtbot.wait(500)
373
374    # --- Assert we're in fixed dir after execution ---
375    with qtbot.waitSignal(shell.executed):
376        shell.execute('import os; current_dir = os.getcwd()')
377    assert shell.get_value('current_dir') == temp_dir
378
379    # ---- Closing test file and resetting config ----
380    main_window.editor.close_file()
381    CONF.set('run', 'configurations', [])
382
383
384@pytest.mark.slow
385@flaky(max_runs=3)
386@pytest.mark.skipif(os.name == 'nt' and PY2, reason="It's failing there")
387def test_dedicated_consoles(main_window, qtbot):
388    """Test running code in dedicated consoles."""
389    # ---- Load test file ----
390    test_file = osp.join(LOCATION, 'script.py')
391    main_window.editor.load(test_file)
392    code_editor = main_window.editor.get_focus_widget()
393
394    # --- Set run options for this file ---
395    rc = RunConfiguration().get()
396    # A dedicated console is used when these two options are False
397    rc['current'] = rc['systerm'] = False
398    config_entry = (test_file, rc)
399    CONF.set('run', 'configurations', [config_entry])
400
401    # --- Run test file and assert that we get a dedicated console ---
402    qtbot.keyClick(code_editor, Qt.Key_F5)
403    qtbot.wait(500)
404    shell = main_window.ipyconsole.get_current_shellwidget()
405    control = shell._control
406    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
407    nsb = main_window.variableexplorer.get_focus_widget()
408
409    assert len(main_window.ipyconsole.get_clients()) == 2
410    assert main_window.ipyconsole.filenames == ['', test_file]
411    assert main_window.ipyconsole.tabwidget.tabText(1) == 'script.py/A'
412    qtbot.wait(500)
413    assert nsb.editor.model.rowCount() == 3
414
415    # --- Assert only runfile text is present and there's no banner text ---
416    # See PR #5301
417    text = control.toPlainText()
418    assert ('runfile' in text) and not ('Python' in text or 'IPython' in text)
419
420    # --- Clean namespace after re-execution ---
421    with qtbot.waitSignal(shell.executed):
422        shell.execute('zz = -1')
423    qtbot.keyClick(code_editor, Qt.Key_F5)
424    qtbot.wait(500)
425    assert not shell.is_defined('zz')
426
427    # --- Assert runfile text is present after reruns ---
428    assert 'runfile' in control.toPlainText()
429
430    # ---- Closing test file and resetting config ----
431    main_window.editor.close_file()
432    CONF.set('run', 'configurations', [])
433
434
435@pytest.mark.slow
436@flaky(max_runs=3)
437def test_connection_to_external_kernel(main_window, qtbot):
438    """Test that only Spyder kernels are connected to the Variable Explorer."""
439    # Test with a generic kernel
440    km, kc = start_new_kernel()
441
442    main_window.ipyconsole._create_client_for_kernel(kc.connection_file, None,
443                                                     None, None)
444    shell = main_window.ipyconsole.get_current_shellwidget()
445    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
446    with qtbot.waitSignal(shell.executed):
447        shell.execute('a = 10')
448
449    # Assert that there are no variables in the variable explorer
450    main_window.variableexplorer.visibility_changed(True)
451    nsb = main_window.variableexplorer.get_focus_widget()
452    qtbot.wait(500)
453    assert nsb.editor.model.rowCount() == 0
454
455    # Test with a kernel from Spyder
456    spykm, spykc = start_new_kernel(spykernel=True)
457    main_window.ipyconsole._create_client_for_kernel(spykc.connection_file, None,
458                                                     None, None)
459    shell = main_window.ipyconsole.get_current_shellwidget()
460    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
461    with qtbot.waitSignal(shell.executed):
462        shell.execute('a = 10')
463
464    # Assert that a variable is visible in the variable explorer
465    main_window.variableexplorer.visibility_changed(True)
466    nsb = main_window.variableexplorer.get_focus_widget()
467    qtbot.wait(500)
468    assert nsb.editor.model.rowCount() == 1
469
470    # Shutdown the kernels
471    spykm.shutdown_kernel(now=True)
472    km.shutdown_kernel(now=True)
473
474
475@pytest.mark.slow
476@flaky(max_runs=3)
477@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
478def test_np_threshold(main_window, qtbot):
479    """Test that setting Numpy threshold doesn't make the Variable Explorer slow."""
480    # Set Numpy threshold
481    shell = main_window.ipyconsole.get_current_shellwidget()
482    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
483    with qtbot.waitSignal(shell.executed):
484        shell.execute('import numpy as np; np.set_printoptions(threshold=np.nan)')
485
486    # Create a big Numpy array
487    with qtbot.waitSignal(shell.executed):
488        shell.execute('x = np.random.rand(75000,5)')
489
490    # Wait a very small time to see the array in the Variable Explorer
491    main_window.variableexplorer.visibility_changed(True)
492    nsb = main_window.variableexplorer.get_focus_widget()
493    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 1, timeout=500)
494
495    # Assert that NumPy threshold remains the same as the one
496    # set by the user
497    with qtbot.waitSignal(shell.executed):
498        shell.execute("t = np.get_printoptions()['threshold']")
499    assert np.isnan(shell.get_value('t'))
500
501
502@pytest.mark.slow
503@flaky(max_runs=3)
504@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
505def test_change_types_in_varexp(main_window, qtbot):
506    """Test that variable types can't be changed in the Variable Explorer."""
507    # Create object
508    shell = main_window.ipyconsole.get_current_shellwidget()
509    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
510    with qtbot.waitSignal(shell.executed):
511        shell.execute('a = 10')
512
513    # Edit object
514    main_window.variableexplorer.visibility_changed(True)
515    nsb = main_window.variableexplorer.get_focus_widget()
516    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() > 0, timeout=EVAL_TIMEOUT)
517    nsb.editor.setFocus()
518    nsb.editor.edit_item()
519
520    # Try to change types
521    qtbot.keyClicks(QApplication.focusWidget(), "'s'")
522    qtbot.keyClick(QApplication.focusWidget(), Qt.Key_Enter)
523    qtbot.wait(1000)
524
525    # Assert object remains the same
526    assert shell.get_value('a') == 10
527
528
529@pytest.mark.slow
530@flaky(max_runs=3)
531@pytest.mark.parametrize("test_directory", [u"non_ascii_ñ_í_ç", u"test_dir"])
532def test_change_cwd_ipython_console(main_window, qtbot, tmpdir, test_directory):
533    """
534    Test synchronization with working directory and File Explorer when
535    changing cwd in the IPython console.
536    """
537    wdir = main_window.workingdirectory
538    treewidget = main_window.explorer.treewidget
539    shell = main_window.ipyconsole.get_current_shellwidget()
540
541    # Wait until the window is fully up
542    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
543
544    # Create temp dir
545    temp_dir = to_text_string(tmpdir.mkdir(test_directory))
546
547    # Change directory in IPython console using %cd
548    with qtbot.waitSignal(shell.executed):
549        shell.execute(u"%cd {}".format(temp_dir))
550    qtbot.wait(1000)
551
552    # Assert that cwd changed in workingdirectory
553    assert osp.normpath(wdir.history[-1]) == osp.normpath(temp_dir)
554
555    # Assert that cwd changed in explorer
556    assert osp.normpath(treewidget.get_current_folder()) == osp.normpath(temp_dir)
557
558
559@pytest.mark.slow
560@flaky(max_runs=3)
561@pytest.mark.parametrize("test_directory", [u"non_ascii_ñ_í_ç", u"test_dir"])
562def test_change_cwd_explorer(main_window, qtbot, tmpdir, test_directory):
563    """
564    Test synchronization with working directory and IPython console when
565    changing directories in the File Explorer.
566    """
567    wdir = main_window.workingdirectory
568    explorer = main_window.explorer
569    shell = main_window.ipyconsole.get_current_shellwidget()
570
571    # Wait until the window is fully up
572    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
573
574    # Create temp directory
575    temp_dir = to_text_string(tmpdir.mkdir(test_directory))
576
577    # Change directory in the explorer widget
578    explorer.chdir(temp_dir)
579    qtbot.wait(1000)
580
581    # Assert that cwd changed in workingdirectory
582    assert osp.normpath(wdir.history[-1]) == osp.normpath(temp_dir)
583
584    # Assert that cwd changed in IPython console
585    assert osp.normpath(temp_dir) == osp.normpath(shell._cwd)
586
587
588@pytest.mark.slow
589@flaky(max_runs=3)
590@pytest.mark.skipif(os.name == 'nt' or not is_module_installed('Cython'),
591                    reason="It times out sometimes on Windows and Cython is needed")
592def test_run_cython_code(main_window, qtbot):
593    """Test all the different ways we have to run Cython code"""
594    # ---- Setup ----
595    # Wait until the window is fully up
596    shell = main_window.ipyconsole.get_current_shellwidget()
597    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
598
599    # Get a reference to the namespace browser widget
600    nsb = main_window.variableexplorer.get_focus_widget()
601
602    # Get a reference to the code editor widget
603    code_editor = main_window.editor.get_focus_widget()
604
605    # ---- Run pyx file ----
606    # Load test file
607    main_window.editor.load(osp.join(LOCATION, 'pyx_script.pyx'))
608
609    # run file
610    qtbot.keyClick(code_editor, Qt.Key_F5)
611    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 1,
612                    timeout=COMPILE_AND_EVAL_TIMEOUT)
613
614    # Verify result
615    assert shell.get_value('a') == 3628800
616
617    # Reset and close file
618    reset_run_code(qtbot, shell, code_editor, nsb)
619    main_window.editor.close_file()
620
621    # ---- Import pyx file ----
622    # Load test file
623    main_window.editor.load(osp.join(LOCATION, 'pyx_lib_import.py'))
624
625    # Run file
626    qtbot.keyClick(code_editor, Qt.Key_F5)
627
628    # Wait until all objects have appeared in the variable explorer
629    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 1,
630                    timeout=COMPILE_AND_EVAL_TIMEOUT)
631
632    # Verify result
633    assert shell.get_value('b') == 3628800
634
635    # Close file
636    main_window.editor.close_file()
637
638
639@pytest.mark.slow
640@flaky(max_runs=3)
641@pytest.mark.skipif(os.name == 'nt', reason="It fails on Windows.")
642def test_open_notebooks_from_project_explorer(main_window, qtbot, tmpdir):
643    """Test that notebooks are open from the Project explorer."""
644    projects = main_window.projects
645    editorstack = main_window.editor.get_current_editorstack()
646
647    # Create a temp project directory
648    project_dir = to_text_string(tmpdir.mkdir('test'))
649
650    # Create an empty notebook in the project dir
651    nb = osp.join(LOCATION, 'notebook.ipynb')
652    shutil.copy(nb, osp.join(project_dir, 'notebook.ipynb'))
653
654    # Create project
655    with qtbot.waitSignal(projects.sig_project_loaded):
656        projects._create_project(project_dir)
657
658    # Select notebook in the project explorer
659    idx = projects.treewidget.get_index('notebook.ipynb')
660    projects.treewidget.setCurrentIndex(idx)
661
662    # Prese Enter there
663    qtbot.keyClick(projects.treewidget, Qt.Key_Enter)
664
665    # Assert that notebook was open
666    assert 'notebook.ipynb' in editorstack.get_current_filename()
667
668    # Convert notebook to a Python file
669    projects.treewidget.convert_notebook(osp.join(project_dir, 'notebook.ipynb'))
670
671    # Assert notebook was open
672    assert 'untitled0.py' in editorstack.get_current_filename()
673
674    # Assert its contents are the expected ones
675    file_text = editorstack.get_current_editor().toPlainText()
676    assert file_text == '\n# coding: utf-8\n\n# In[1]:\n\n\n1 + 1\n\n\n'
677
678    # Close project
679    projects.close_project()
680
681
682@pytest.mark.slow
683@flaky(max_runs=3)
684@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
685def test_set_new_breakpoints(main_window, qtbot):
686    """Test that new breakpoints are set in the IPython console."""
687    # Wait until the window is fully up
688    shell = main_window.ipyconsole.get_current_shellwidget()
689    control = shell._control
690    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
691
692    # Clear all breakpoints
693    main_window.editor.clear_all_breakpoints()
694
695    # Load test file
696    test_file = osp.join(LOCATION, 'script.py')
697    main_window.editor.load(test_file)
698
699    # Click the debug button
700    debug_action = main_window.debug_toolbar_actions[0]
701    debug_button = main_window.debug_toolbar.widgetForAction(debug_action)
702    qtbot.mouseClick(debug_button, Qt.LeftButton)
703    qtbot.wait(1000)
704
705    # Set a breakpoint
706    code_editor = main_window.editor.get_focus_widget()
707    code_editor.add_remove_breakpoint(line_number=6)
708    qtbot.wait(500)
709
710    # Verify that the breakpoint was set
711    shell.kernel_client.input("b")
712    qtbot.wait(500)
713    assert "1   breakpoint   keep yes   at {}:6".format(test_file) in control.toPlainText()
714
715    # Remove breakpoint and close test file
716    main_window.editor.clear_all_breakpoints()
717    main_window.editor.close_file()
718
719
720@pytest.mark.slow
721@flaky(max_runs=3)
722@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
723def test_run_code(main_window, qtbot):
724    """Test all the different ways we have to run code"""
725    # ---- Setup ----
726    # Wait until the window is fully up
727    shell = main_window.ipyconsole.get_current_shellwidget()
728    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
729
730    # Load test file
731    main_window.editor.load(osp.join(LOCATION, 'script.py'))
732
733    # Move to the editor's first line
734    code_editor = main_window.editor.get_focus_widget()
735    code_editor.setFocus()
736    qtbot.keyClick(code_editor, Qt.Key_Home, modifier=Qt.ControlModifier)
737
738    # Get a reference to the namespace browser widget
739    nsb = main_window.variableexplorer.get_focus_widget()
740
741    # ---- Run file ----
742    qtbot.keyClick(code_editor, Qt.Key_F5)
743
744    # Wait until all objects have appeared in the variable explorer
745    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 3, timeout=EVAL_TIMEOUT)
746
747    # Verify result
748    assert shell.get_value('a') == 10
749    assert shell.get_value('li') == [1, 2, 3]
750    assert_array_equal(shell.get_value('arr'), np.array([1, 2, 3]))
751
752    reset_run_code(qtbot, shell, code_editor, nsb)
753
754    # ---- Run lines ----
755    # Run the whole file line by line
756    for _ in range(code_editor.blockCount()):
757        qtbot.keyClick(code_editor, Qt.Key_F9)
758        qtbot.wait(100)
759
760    # Wait until all objects have appeared in the variable explorer
761    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 3, timeout=EVAL_TIMEOUT)
762
763    # Verify result
764    assert shell.get_value('a') == 10
765    assert shell.get_value('li') == [1, 2, 3]
766    assert_array_equal(shell.get_value('arr'), np.array([1, 2, 3]))
767
768    reset_run_code(qtbot, shell, code_editor, nsb)
769
770    # ---- Run cell and advance ----
771    # Run the three cells present in file
772    for _ in range(3):
773        qtbot.keyClick(code_editor, Qt.Key_Return, modifier=Qt.ShiftModifier)
774        qtbot.wait(100)
775
776    # Wait until all objects have appeared in the variable explorer
777    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 3, timeout=EVAL_TIMEOUT)
778
779    # Verify result
780    assert shell.get_value('a') == 10
781    assert shell.get_value('li') == [1, 2, 3]
782    assert_array_equal(shell.get_value('arr'), np.array([1, 2, 3]))
783
784    reset_run_code(qtbot, shell, code_editor, nsb)
785
786    # ---- Run cell ----
787    # Run the first cell in file
788    qtbot.keyClick(code_editor, Qt.Key_Return, modifier=Qt.ControlModifier)
789
790    # Wait until the object has appeared in the variable explorer
791    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 1, timeout=EVAL_TIMEOUT)
792
793    # Verify result
794    assert shell.get_value('a') == 10
795
796    # Press Ctrl+Enter a second time to verify that we're *not* advancing
797    # to the next cell
798    qtbot.keyClick(code_editor, Qt.Key_Return, modifier=Qt.ControlModifier)
799    assert nsb.editor.model.rowCount() == 1
800
801    reset_run_code(qtbot, shell, code_editor, nsb)
802
803    # ---- Re-run last cell ----
804    # Run the first two cells in file
805    qtbot.keyClick(code_editor, Qt.Key_Return, modifier=Qt.ShiftModifier)
806    qtbot.keyClick(code_editor, Qt.Key_Return, modifier=Qt.ShiftModifier)
807
808    # Wait until objects have appeared in the variable explorer
809    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 2, timeout=EVAL_TIMEOUT)
810
811    # Clean namespace
812    with qtbot.waitSignal(shell.executed):
813        shell.execute('%reset -f')
814
815    # Wait until there are no objects in the variable explorer
816    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 0, timeout=EVAL_TIMEOUT)
817
818    # Re-run last cell
819    qtbot.keyClick(code_editor, Qt.Key_Return, modifier=Qt.AltModifier)
820
821    # Wait until the object has appeared in the variable explorer
822    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 1, timeout=EVAL_TIMEOUT)
823    assert shell.get_value('li') == [1, 2, 3]
824
825    # ---- Closing test file ----
826    main_window.editor.close_file()
827
828
829@pytest.mark.slow
830@flaky(max_runs=3)
831@pytest.mark.skipif(os.name == 'nt' or os.environ.get('CI', None) is None or PYQT5,
832                    reason="It times out sometimes on Windows, it's not "
833                           "meant to be run outside of a CI and it segfaults "
834                           "too frequently in PyQt5")
835def test_open_files_in_new_editor_window(main_window, qtbot):
836    """
837    This tests that opening files in a new editor window
838    is working as expected.
839
840    Test for issue 4085
841    """
842    # Set a timer to manipulate the open dialog while it's running
843    QTimer.singleShot(2000, lambda: open_file_in_editor(main_window,
844                                                        'script.py',
845                                                        directory=LOCATION))
846
847    # Create a new editor window
848    # Note: editor.load() uses the current editorstack by default
849    main_window.editor.create_new_window()
850    main_window.editor.load()
851
852    # Perform the test
853    # Note: There's always one file open in the Editor
854    editorstack = main_window.editor.get_current_editorstack()
855    assert editorstack.get_stack_count() == 2
856
857
858@pytest.mark.slow
859@flaky(max_runs=3)
860def test_close_when_file_is_changed(main_window, qtbot):
861    """Test closing spyder when there is a file with modifications open."""
862    # Wait until the window is fully up
863    shell = main_window.ipyconsole.get_current_shellwidget()
864    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
865
866    # Load test file
867    test_file = osp.join(LOCATION, 'script.py')
868    main_window.editor.load(test_file)
869    editorstack = main_window.editor.get_current_editorstack()
870    editor = editorstack.get_current_editor()
871    editor.document().setModified(True)
872
873    # Wait for the segfault
874    qtbot.wait(3000)
875
876
877@pytest.mark.slow
878@flaky(max_runs=3)
879def test_maximize_minimize_plugins(main_window, qtbot):
880    """Test that the maximize button is working correctly."""
881    # Set focus to the Editor
882    main_window.editor.get_focus_widget().setFocus()
883
884    # Click the maximize button
885    max_action = main_window.maximize_action
886    max_button = main_window.main_toolbar.widgetForAction(max_action)
887    qtbot.mouseClick(max_button, Qt.LeftButton)
888
889    # Verify that the Editor is maximized
890    assert main_window.editor.ismaximized
891
892    # Verify that the action minimizes the plugin too
893    qtbot.mouseClick(max_button, Qt.LeftButton)
894    assert not main_window.editor.ismaximized
895
896
897@flaky(max_runs=3)
898@pytest.mark.skipif((os.name == 'nt' or
899                     os.environ.get('CI', None) is not None and PYQT_VERSION >= '5.9'),
900                    reason="It times out on Windows and segfaults in our CIs with PyQt >= 5.9")
901def test_issue_4066(main_window, qtbot):
902    """
903    Test for a segfault when these steps are followed:
904
905    1. Open an object present in the Variable Explorer (e.g. a list).
906    2. Delete that object in its corresponding console while its
907       editor is still opem.
908    3. Closing that editor by pressing its *Ok* button.
909    """
910    # Create the object
911    shell = main_window.ipyconsole.get_current_shellwidget()
912    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
913    with qtbot.waitSignal(shell.executed):
914        shell.execute('myobj = [1, 2, 3]')
915
916    # Open editor associated with that object and get a reference to it
917    nsb = main_window.variableexplorer.get_focus_widget()
918    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() > 0, timeout=EVAL_TIMEOUT)
919    nsb.editor.setFocus()
920    nsb.editor.edit_item()
921    obj_editor_id = list(nsb.editor.delegate._editors.keys())[0]
922    obj_editor = nsb.editor.delegate._editors[obj_editor_id]['editor']
923
924    # Move to the IPython console and delete that object
925    main_window.ipyconsole.get_focus_widget().setFocus()
926    with qtbot.waitSignal(shell.executed):
927        shell.execute('del myobj')
928    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() == 0, timeout=EVAL_TIMEOUT)
929
930    # Close editor
931    ok_widget = obj_editor.bbox.button(obj_editor.bbox.Ok)
932    qtbot.mouseClick(ok_widget, Qt.LeftButton)
933
934    # Wait for the segfault
935    qtbot.wait(3000)
936
937
938@pytest.mark.slow
939@flaky(max_runs=3)
940@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
941def test_varexp_edit_inline(main_window, qtbot):
942    """
943    Test for errors when editing inline values in the Variable Explorer
944    and then moving to another plugin.
945
946    Note: Errors for this test don't appear related to it but instead they
947    are shown down the road. That's because they are generated by an
948    async C++ RuntimeError.
949    """
950    # Create object
951    shell = main_window.ipyconsole.get_current_shellwidget()
952    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
953    with qtbot.waitSignal(shell.executed):
954        shell.execute('a = 10')
955
956    # Edit object
957    main_window.variableexplorer.visibility_changed(True)
958    nsb = main_window.variableexplorer.get_focus_widget()
959    qtbot.waitUntil(lambda: nsb.editor.model.rowCount() > 0, timeout=EVAL_TIMEOUT)
960    nsb.editor.setFocus()
961    nsb.editor.edit_item()
962
963    # Change focus to IPython console
964    main_window.ipyconsole.get_focus_widget().setFocus()
965
966    # Wait for the error
967    qtbot.wait(3000)
968
969
970@pytest.mark.slow
971@flaky(max_runs=3)
972@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
973def test_c_and_n_pdb_commands(main_window, qtbot):
974    """Test that c and n Pdb commands update the Variable Explorer."""
975    nsb = main_window.variableexplorer.get_focus_widget()
976
977    # Wait until the window is fully up
978    shell = main_window.ipyconsole.get_current_shellwidget()
979    control = shell._control
980    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
981
982    # Clear all breakpoints
983    main_window.editor.clear_all_breakpoints()
984
985    # Load test file
986    test_file = osp.join(LOCATION, 'script.py')
987    main_window.editor.load(test_file)
988
989    # Click the debug button
990    debug_action = main_window.debug_toolbar_actions[0]
991    debug_button = main_window.debug_toolbar.widgetForAction(debug_action)
992    qtbot.mouseClick(debug_button, Qt.LeftButton)
993    qtbot.wait(1000)
994
995    # Set a breakpoint
996    code_editor = main_window.editor.get_focus_widget()
997    code_editor.add_remove_breakpoint(line_number=6)
998    qtbot.wait(500)
999
1000    # Verify that c works
1001    qtbot.keyClicks(control, 'c')
1002    qtbot.keyClick(control, Qt.Key_Enter)
1003    qtbot.wait(500)
1004    assert nsb.editor.model.rowCount() == 1
1005
1006    # Verify that n works
1007    qtbot.keyClicks(control, 'n')
1008    qtbot.keyClick(control, Qt.Key_Enter)
1009    qtbot.wait(500)
1010    assert nsb.editor.model.rowCount() == 2
1011
1012    # Verify that doesn't go to sitecustomize.py with next and stops
1013    # the debugging session.
1014    qtbot.keyClicks(control, 'n')
1015    qtbot.keyClick(control, Qt.Key_Enter)
1016    qtbot.wait(500)
1017
1018    qtbot.keyClicks(control, 'n')
1019    qtbot.keyClick(control, Qt.Key_Enter)
1020    qtbot.wait(500)
1021
1022    assert nsb.editor.model.rowCount() == 3
1023
1024    qtbot.keyClicks(control, 'n')
1025    qtbot.keyClick(control, Qt.Key_Enter)
1026    qtbot.wait(500)
1027
1028    # Assert that the prompt appear
1029    shell.clear_console()
1030    assert 'In [2]:' in control.toPlainText()
1031
1032    # Remove breakpoint and close test file
1033    main_window.editor.clear_all_breakpoints()
1034    main_window.editor.close_file()
1035
1036
1037@pytest.mark.slow
1038@flaky(max_runs=3)
1039@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
1040def test_stop_dbg(main_window, qtbot):
1041    """Test that we correctly stop a debugging session."""
1042    nsb = main_window.variableexplorer.get_focus_widget()
1043
1044    # Wait until the window is fully up
1045    shell = main_window.ipyconsole.get_current_shellwidget()
1046    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
1047
1048    # Clear all breakpoints
1049    main_window.editor.clear_all_breakpoints()
1050
1051    # Load test file
1052    test_file = osp.join(LOCATION, 'script.py')
1053    main_window.editor.load(test_file)
1054
1055    # Click the debug button
1056    debug_action = main_window.debug_toolbar_actions[0]
1057    debug_button = main_window.debug_toolbar.widgetForAction(debug_action)
1058    qtbot.mouseClick(debug_button, Qt.LeftButton)
1059    qtbot.wait(1000)
1060
1061    # Move to the next line
1062    shell.kernel_client.input("n")
1063    qtbot.wait(1000)
1064
1065    # Stop debugging
1066    stop_debug_action = main_window.debug_toolbar_actions[5]
1067    stop_debug_button = main_window.debug_toolbar.widgetForAction(stop_debug_action)
1068    qtbot.mouseClick(stop_debug_button, Qt.LeftButton)
1069    qtbot.wait(1000)
1070
1071    # Assert that there is only one entry in the Variable Explorer
1072    assert nsb.editor.model.rowCount() == 1
1073
1074    # Remove breakpoint and close test file
1075    main_window.editor.clear_all_breakpoints()
1076    main_window.editor.close_file()
1077
1078
1079@pytest.mark.slow
1080@flaky(max_runs=3)
1081@pytest.mark.skipif(os.name == 'nt', reason="It times out sometimes on Windows")
1082def test_change_cwd_dbg(main_window, qtbot):
1083    """
1084    Test that using the Working directory toolbar is working while debugging.
1085    """
1086    # Wait until the window is fully up
1087    shell = main_window.ipyconsole.get_current_shellwidget()
1088    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
1089
1090    # Give focus to the widget that's going to receive clicks
1091    control = main_window.ipyconsole.get_focus_widget()
1092    control.setFocus()
1093
1094    # Import os to get cwd
1095    with qtbot.waitSignal(shell.executed):
1096        shell.execute('import os')
1097
1098    # Click the debug button
1099    debug_action = main_window.debug_toolbar_actions[0]
1100    debug_button = main_window.debug_toolbar.widgetForAction(debug_action)
1101    qtbot.mouseClick(debug_button, Qt.LeftButton)
1102    qtbot.wait(1000)
1103
1104    # Set LOCATION as cwd
1105    main_window.workingdirectory.chdir(tempfile.gettempdir(),
1106                                       browsing_history=False,
1107                                       refresh_explorer=True)
1108    qtbot.wait(1000)
1109
1110    # Get cwd in console
1111    qtbot.keyClicks(control, 'os.getcwd()')
1112    qtbot.keyClick(control, Qt.Key_Enter)
1113    qtbot.wait(1000)
1114
1115    # Assert cwd is the right one
1116    assert tempfile.gettempdir() in control.toPlainText()
1117
1118
1119@pytest.mark.slow
1120@flaky(max_runs=3)
1121@pytest.mark.skipif(os.name == 'nt' or PY2, reason="It times out sometimes")
1122def test_varexp_magic_dbg(main_window, qtbot):
1123    """Test that %varexp is working while debugging."""
1124    nsb = main_window.variableexplorer.get_focus_widget()
1125
1126    # Wait until the window is fully up
1127    shell = main_window.ipyconsole.get_current_shellwidget()
1128    qtbot.waitUntil(lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)
1129
1130    # Give focus to the widget that's going to receive clicks
1131    control = main_window.ipyconsole.get_focus_widget()
1132    control.setFocus()
1133
1134    # Create an object that can be plotted
1135    with qtbot.waitSignal(shell.executed):
1136        shell.execute('li = [1, 2, 3]')
1137
1138    # Click the debug button
1139    debug_action = main_window.debug_toolbar_actions[0]
1140    debug_button = main_window.debug_toolbar.widgetForAction(debug_action)
1141    qtbot.mouseClick(debug_button, Qt.LeftButton)
1142    qtbot.wait(1000)
1143
1144    # Generate the plot from the Variable Explorer
1145    nsb.plot('li', 'plot')
1146    qtbot.wait(1000)
1147
1148    # Assert that there's a plot in the console
1149    assert shell._control.toHtml().count('img src') == 1
1150
1151
1152@pytest.mark.slow
1153@flaky(max_runs=3)
1154@pytest.mark.skipif(os.environ.get('CI', None) is None,
1155                    reason="It's not meant to be run outside of a CI")
1156def test_fileswitcher(main_window, qtbot):
1157    """Test the use of shorten paths when necessary in the fileswitcher."""
1158    # Load tests files
1159    dir_b = osp.join(TEMP_DIRECTORY, 'temp_dir_a', 'temp_b')
1160    filename_b =  osp.join(dir_b, 'c.py')
1161    if not osp.isdir(dir_b):
1162        os.makedirs(dir_b)
1163    if not osp.isfile(filename_b):
1164        file_c = open(filename_b, 'w+')
1165        file_c.close()
1166    if PYQT5:
1167        dir_d = osp.join(TEMP_DIRECTORY, 'temp_dir_a', 'temp_c', 'temp_d', 'temp_e')
1168    else:
1169        dir_d = osp.join(TEMP_DIRECTORY, 'temp_dir_a', 'temp_c', 'temp_d')
1170        dir_e = osp.join(TEMP_DIRECTORY, 'temp_dir_a', 'temp_c', 'temp_dir_f', 'temp_e')
1171        filename_e = osp.join(dir_e, 'a.py')
1172        if not osp.isdir(dir_e):
1173            os.makedirs(dir_e)
1174        if not osp.isfile(filename_e):
1175            file_e = open(filename_e, 'w+')
1176            file_e.close()
1177    filename_d =  osp.join(dir_d, 'c.py')
1178    if not osp.isdir(dir_d):
1179        os.makedirs(dir_d)
1180    if not osp.isfile(filename_d):
1181        file_d = open(filename_d, 'w+')
1182        file_d.close()
1183    main_window.editor.load(filename_b)
1184    main_window.editor.load(filename_d)
1185
1186    # Assert that all the path of the file is shown
1187    main_window.open_fileswitcher()
1188    if os.name == 'nt':
1189        item_text = main_window.fileswitcher.list.currentItem().text().replace('\\', '/').lower()
1190        dir_d = dir_d.replace('\\', '/').lower()
1191    else:
1192        item_text = main_window.fileswitcher.list.currentItem().text()
1193    assert dir_d in item_text
1194
1195    # Resize Main Window to a third of its width
1196    size = main_window.window_size
1197    main_window.resize(size.width() / 3, size.height())
1198    main_window.open_fileswitcher()
1199
1200    # Assert that the path shown in the fileswitcher is shorter
1201    if PYQT5:
1202        main_window.open_fileswitcher()
1203        item_text = main_window.fileswitcher.list.currentItem().text()
1204        assert '...' in item_text
1205
1206
1207@pytest.mark.slow
1208@flaky(max_runs=3)
1209@pytest.mark.skipif(not PYQT5, reason="It times out.")
1210def test_run_static_code_analysis(main_window, qtbot):
1211    """This tests that the Pylint plugin is working as expected."""
1212    # Wait until the window is fully up
1213    shell = main_window.ipyconsole.get_current_shellwidget()
1214    qtbot.waitUntil(lambda: shell._prompt_html is not None,
1215                    timeout=SHELL_TIMEOUT)
1216
1217    # Select the third-party plugin
1218    pylint = get_thirdparty_plugin(main_window, "Static code analysis")
1219
1220    # Do an analysis
1221    test_file = osp.join(LOCATION, 'script_pylint.py')
1222    main_window.editor.load(test_file)
1223    code_editor = main_window.editor.get_focus_widget()
1224    qtbot.keyClick(code_editor, Qt.Key_F8)
1225    qtbot.wait(3000)
1226
1227    # Perform the test
1228    # Check output of the analysis
1229    treewidget = pylint.get_focus_widget()
1230    qtbot.waitUntil(lambda: treewidget.results is not None,
1231                    timeout=SHELL_TIMEOUT)
1232    result_content = treewidget.results
1233    assert result_content['C:']
1234    assert len(result_content['C:']) == 5
1235
1236    # Close the file
1237    main_window.editor.close_file()
1238
1239
1240@flaky(max_runs=3)
1241def test_troubleshooting_menu_item_and_url(monkeypatch):
1242    """Test that the troubleshooting menu item calls the valid URL."""
1243    MockMainWindow = MagicMock(spec=MainWindow)
1244    mockMainWindow_instance = MockMainWindow()
1245    mockMainWindow_instance.__class__ = MainWindow
1246    MockQDesktopServices = Mock()
1247    mockQDesktopServices_instance = MockQDesktopServices()
1248    attr_to_patch = ('spyder.app.mainwindow.QDesktopServices')
1249    monkeypatch.setattr(attr_to_patch, MockQDesktopServices)
1250
1251    # Unit test of help menu item: Make sure the correct URL is called.
1252    MainWindow.trouble_guide(mockMainWindow_instance)
1253    assert MockQDesktopServices.openUrl.call_count == 1
1254    mockQDesktopServices_instance.openUrl.called_once_with(__trouble_url__)
1255
1256    # Check that the URL resolves correctly. Ignored if no internet connection.
1257    try:
1258        urlopen("https://www.github.com", timeout=1)
1259    except Exception:
1260        pass
1261    else:
1262        try:
1263            urlopen(__trouble_url__, timeout=1)
1264        except URLError:
1265            raise
1266
1267
1268@flaky(max_runs=3)
1269@pytest.mark.slow
1270def test_tabfilter_typeerror_full(main_window):
1271    """Test for #5813 ; event filter handles None indicies when moving tabs."""
1272    MockEvent = MagicMock()
1273    MockEvent.return_value.type.return_value = QEvent.MouseMove
1274    MockEvent.return_value.pos.return_value = 0
1275    mockEvent_instance = MockEvent()
1276
1277    test_tabbar = main_window.findChildren(QTabBar)[0]
1278    test_tabfilter = TabFilter(test_tabbar, main_window)
1279    test_tabfilter.from_index = None
1280    test_tabfilter.moving = True
1281
1282    assert test_tabfilter.eventFilter(None, mockEvent_instance)
1283    assert mockEvent_instance.pos.call_count == 1
1284
1285
1286@flaky(max_runs=3)
1287@pytest.mark.slow
1288def test_help_opens_when_show_tutorial_full(main_window, qtbot):
1289    """Test fix for #6317 : 'Show tutorial' opens the help plugin if closed."""
1290    HELP_STR = "Help"
1291
1292    # Wait until the window is fully up
1293    shell = main_window.ipyconsole.get_current_shellwidget()
1294    qtbot.waitUntil(lambda: shell._prompt_html is not None,
1295                    timeout=SHELL_TIMEOUT)
1296
1297    help_pane_menuitem = None
1298    for action in main_window.plugins_menu.actions():
1299        if action.text() == HELP_STR:
1300            help_pane_menuitem = action
1301            break
1302
1303    # Test opening tutorial with Help plguin closed
1304    try:
1305        main_window.help.plugin_closed()
1306    except Exception:
1307        pass
1308    qtbot.wait(500)
1309    help_tabbar, help_index = find_desired_tab_in_window(HELP_STR, main_window)
1310
1311    assert help_tabbar is None and help_index is None
1312    assert not isinstance(main_window.focusWidget(), ObjectComboBox)
1313    assert not help_pane_menuitem.isChecked()
1314
1315    main_window.help.show_tutorial()
1316    qtbot.wait(500)
1317
1318    help_tabbar, help_index = find_desired_tab_in_window(HELP_STR, main_window)
1319    assert None not in (help_tabbar, help_index)
1320    assert help_index == help_tabbar.currentIndex()
1321    assert isinstance(main_window.focusWidget(), ObjectComboBox)
1322    assert help_pane_menuitem.isChecked()
1323
1324    # Test opening tutorial with help plugin open, but not selected
1325    help_tabbar.setCurrentIndex((help_tabbar.currentIndex() + 1)
1326                                % help_tabbar.count())
1327    qtbot.wait(500)
1328    help_tabbar, help_index = find_desired_tab_in_window(HELP_STR, main_window)
1329    assert None not in (help_tabbar, help_index)
1330    assert help_index != help_tabbar.currentIndex()
1331    assert not isinstance(main_window.focusWidget(), ObjectComboBox)
1332    assert help_pane_menuitem.isChecked()
1333
1334    main_window.help.show_tutorial()
1335    qtbot.wait(500)
1336    help_tabbar, help_index = find_desired_tab_in_window(HELP_STR, main_window)
1337    assert None not in (help_tabbar, help_index)
1338    assert help_index == help_tabbar.currentIndex()
1339    assert isinstance(main_window.focusWidget(), ObjectComboBox)
1340    assert help_pane_menuitem.isChecked()
1341
1342    # Test opening tutorial with help plugin open and the active tab
1343    qtbot.wait(500)
1344    main_window.help.show_tutorial()
1345    help_tabbar, help_index = find_desired_tab_in_window(HELP_STR, main_window)
1346    qtbot.wait(500)
1347    assert None not in (help_tabbar, help_index)
1348    assert help_index == help_tabbar.currentIndex()
1349    assert isinstance(main_window.focusWidget(), ObjectComboBox)
1350    assert help_pane_menuitem.isChecked()
1351
1352
1353def test_report_issue_url(monkeypatch):
1354    """Test that report_issue sends the data, and to correct url."""
1355    body = 'This is an example error report body text.'
1356    title = 'Uncreative issue title here'
1357    body_autogenerated = 'Auto-generated text.'
1358    target_url_base = __project_url__ + '/issues/new'
1359
1360    MockMainWindow = MagicMock(spec=MainWindow)
1361    mockMainWindow_instance = MockMainWindow()
1362    mockMainWindow_instance.__class__ = MainWindow
1363    mockMainWindow_instance.render_issue.return_value = body_autogenerated
1364
1365    MockQDesktopServices = MagicMock()
1366    mockQDesktopServices_instance = MockQDesktopServices()
1367    attr_to_patch = ('spyder.app.mainwindow.QDesktopServices')
1368    monkeypatch.setattr(attr_to_patch, MockQDesktopServices)
1369
1370    # Test when body=None, i.e. when Help > Report Issue is chosen
1371    target_url = QUrl(target_url_base + '?body=' + body_autogenerated)
1372    MainWindow.report_issue(mockMainWindow_instance, body=None, title=None)
1373    assert MockQDesktopServices.openUrl.call_count == 1
1374    MockQDesktopServices.openUrl.assert_called_once_with(target_url)
1375
1376    # Test with body=None and title != None
1377    target_url = QUrl(target_url_base + '?body=' + body_autogenerated
1378                      + "&title=" + title)
1379    MainWindow.report_issue(mockMainWindow_instance, body=None, title=title)
1380    assert MockQDesktopServices.openUrl.call_count == 2
1381    mockQDesktopServices_instance.openUrl.called_with(target_url)
1382
1383    # Test when body != None, i.e. when auto-submitting error to Github
1384    target_url = QUrl(target_url_base + '?body=' + body)
1385    MainWindow.report_issue(mockMainWindow_instance, body=body, title=None)
1386    assert MockQDesktopServices.openUrl.call_count == 3
1387    mockQDesktopServices_instance.openUrl.called_with(target_url)
1388
1389    # Test when body != None and title != None
1390    target_url = QUrl(target_url_base + '?body=' + body
1391                      + "&title=" + title)
1392    MainWindow.report_issue(mockMainWindow_instance, body=body, title=title)
1393    assert MockQDesktopServices.openUrl.call_count == 4
1394    mockQDesktopServices_instance.openUrl.called_with(target_url)
1395
1396
1397def test_render_issue():
1398    """Test that render issue works without errors and returns text."""
1399    test_description = "This is a test description"
1400    test_traceback = "An error occured. Oh no!"
1401
1402    MockMainWindow = MagicMock(spec=MainWindow)
1403    mockMainWindow_instance = MockMainWindow()
1404    mockMainWindow_instance.__class__ = MainWindow
1405
1406    # Test when description and traceback are not provided
1407    test_issue_1 = MainWindow.render_issue(mockMainWindow_instance)
1408    assert type(test_issue_1) == str
1409    assert len(test_issue_1) > 100
1410
1411    # Test when description and traceback are provided
1412    test_issue_2 = MainWindow.render_issue(mockMainWindow_instance,
1413                                           test_description, test_traceback)
1414    assert type(test_issue_2) == str
1415    assert len(test_issue_2) > 100
1416    assert test_description in test_issue_2
1417    assert test_traceback in test_issue_2
1418
1419
1420if __name__ == "__main__":
1421    pytest.main()
1422