-
Notifications
You must be signed in to change notification settings - Fork 452
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6828 from kozlovsky/fix/tribler_start_shutdown_gu…
…i_tests Fix Tribler startup/shutdown and GUI tests stability
- Loading branch information
Showing
10 changed files
with
155 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
from typing import Optional | ||
|
||
from PyQt5.QtWidgets import QApplication | ||
|
||
from tribler.gui.utilities import connect | ||
|
||
|
||
class AppManager: | ||
""" | ||
A helper class that calls QApplication.quit() | ||
You should never call `QApplication.quit()` directly. Call `app_manager.quit_application()` instead. | ||
It is necessary to avoid runtime errors like "wrapped C/C++ object of type ... has been deleted". | ||
After `app_manager.quit_application()` was called, it is not safe to access Qt objects anymore. | ||
If a signal can be emitted during the application shutdown, you can check `app_manager.quitting_app` flag | ||
inside the signal handler to be sure that it is still safe to access Qt objects. | ||
""" | ||
|
||
def __init__(self, app: Optional[QApplication] = None): | ||
self.quitting_app = False | ||
if app is not None: | ||
# app can be None in tests where Qt application is not created | ||
connect(app.aboutToQuit, self.on_about_to_quit) | ||
|
||
def on_about_to_quit(self): | ||
self.quitting_app = True | ||
|
||
def quit_application(self): | ||
if not self.quitting_app: | ||
self.quitting_app = True | ||
QApplication.quit() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,66 +1,63 @@ | ||
import sys | ||
from unittest.mock import MagicMock, patch | ||
|
||
import pytest | ||
|
||
from tribler.gui.core_manager import CoreCrashedError, CoreManager | ||
|
||
pytestmark = pytest.mark.asyncio | ||
|
||
@pytest.fixture(name='core_manager') | ||
def fixture_core_manager(): | ||
core_manager = CoreManager(root_state_dir=MagicMock(), api_port=MagicMock(), api_key=MagicMock(), | ||
app_manager=MagicMock(), | ||
events_manager=MagicMock()) | ||
core_manager.core_process = MagicMock(readAllStandardOutput=MagicMock(return_value=b'core stdout'), | ||
readAllStandardError=MagicMock(return_value=b'core stderr')) | ||
return core_manager | ||
|
||
# fmt: off | ||
|
||
@patch.object(CoreManager, 'quit_application') | ||
@patch('tribler.gui.core_manager.EventRequestManager', new=MagicMock()) | ||
async def test_on_core_finished_call_on_finished(mocked_quit_application: MagicMock): | ||
def test_on_core_finished_calls_quit_application(core_manager): | ||
# test that in case of `shutting_down` and `should_quit_app_on_core_finished` flags have been set to True | ||
# then `on_finished` function will be called and Exception will not be raised | ||
core_manager = CoreManager(MagicMock(), MagicMock(), MagicMock(), MagicMock()) | ||
# then `quit_application` method will be called and Exception will not be raised | ||
core_manager.shutting_down = True | ||
core_manager.should_quit_app_on_core_finished = True | ||
|
||
core_manager.on_core_finished(exit_code=1, exit_status='exit status') | ||
mocked_quit_application.assert_called_once() | ||
core_manager.app_manager.quit_application.assert_called_once() | ||
|
||
|
||
@patch('tribler.gui.core_manager.EventRequestManager', new=MagicMock()) | ||
async def test_on_core_finished_raises_error(): | ||
def test_on_core_finished_raises_error(core_manager): | ||
# test that in case of flag `shutting_down` has been set to True and | ||
# exit_code is not equal to 0, then CoreRuntimeError should be raised | ||
core_manager = CoreManager(MagicMock(), MagicMock(), MagicMock(), MagicMock()) | ||
|
||
with pytest.raises(CoreCrashedError): | ||
core_manager.on_core_finished(exit_code=1, exit_status='exit status') | ||
|
||
|
||
@patch('tribler.gui.core_manager.print') | ||
@patch('tribler.gui.core_manager.EventRequestManager', new=MagicMock()) | ||
async def test_on_core_stdout_read_ready(mocked_print: MagicMock): | ||
@patch('builtins.print') | ||
def test_on_core_stdout_read_ready(mocked_print, core_manager): | ||
# test that method `on_core_stdout_read_ready` converts byte output to a string and prints it | ||
core_manager = CoreManager(MagicMock(), MagicMock(), MagicMock(), MagicMock()) | ||
core_manager.core_process = MagicMock(readAllStandardOutput=MagicMock(return_value=b'binary string')) | ||
core_manager.app_manager.quitting_app = False | ||
core_manager.on_core_stdout_read_ready() | ||
mocked_print.assert_called_with('binary string') | ||
mocked_print.assert_called_with('core stdout') | ||
|
||
|
||
@patch('tribler.gui.core_manager.print') | ||
@patch('tribler.gui.core_manager.EventRequestManager', new=MagicMock()) | ||
@patch('sys.stderr') | ||
async def test_on_core_stderr_read_ready(mocked_stderr, mocked_print: MagicMock): | ||
@patch('builtins.print') | ||
def test_on_core_stderr_read_ready(mocked_print, core_manager): | ||
# test that method `on_core_stdout_read_ready` converts byte output to a string and prints it | ||
core_manager = CoreManager(MagicMock(), MagicMock(), MagicMock(), MagicMock()) | ||
core_manager.core_process = MagicMock(readAllStandardError=MagicMock(return_value=b'binary string')) | ||
core_manager.app_manager.quitting_app = False | ||
core_manager.on_core_stderr_read_ready() | ||
mocked_print.assert_called_with('binary string', file=mocked_stderr) | ||
mocked_print.assert_called_with('core stderr', file=sys.stderr) | ||
|
||
|
||
@patch('tribler.gui.core_manager.EventRequestManager', new=MagicMock()) | ||
@patch('builtins.print', MagicMock(side_effect=OSError())) | ||
def test_on_core_stdout_stderr_read_ready_os_error(): | ||
# test that OSError on writing to stdout is suppressed when quitting the application | ||
|
||
core_manager = CoreManager(MagicMock(), MagicMock(), MagicMock(), MagicMock()) | ||
core_manager.core_process = MagicMock(read_all=MagicMock(return_value='')) | ||
def test_on_core_read_ready_os_error_suppressed(core_manager): | ||
# OSError exceptions when writing to stdout and stderr are suppressed | ||
core_manager.app_manager.quitting_app = False | ||
core_manager.on_core_stdout_read_ready() | ||
core_manager.on_core_stderr_read_ready() | ||
assert print.call_count == 2 | ||
|
||
# check that OSError exception is suppressed when writing to stdout and stderr | ||
# if app is quitting, core_manager does not write to stdout/stderr at all, and so the call counter does not grow | ||
core_manager.app_manager.quitting_app = True | ||
core_manager.on_core_stdout_read_ready() | ||
core_manager.on_core_stderr_read_ready() | ||
assert print.call_count == 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.