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