簡體   English   中英

如何正確退出隊列和 Qthread 以使用 pytest 進行測試?

[英]How to exit properly a Queue and Qthread for tests with pytest?

我用PyQt5創建了一個 GUI,我想通過pytest測試它。

我的 GUI 需要重定向標准輸出,因此我使用Qthread創建偵聽器。 此偵聽器將stdout放入Queue並發送由 GUI 使用的信號。

到這里為止,沒有問題。 當我要退出什么時,我的問題出現了; 當我退出使用 python 解釋器時,我沒有問題,但是當我使用pytest我收到EOFError或一條消息,說我殺死了一個正在運行的線程。 我試圖正確退出,但問題仍然存在,所以我來尋求幫助。

這是GUI.py的示例:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys

from functools import partial
import multiprocessing
from PyQt5 import QtGui, QtCore, QtWidgets, QtTest

from PyQt5.QtWidgets import *
from PyQt5.QtCore import QCoreApplication, Qt, QObject, pyqtSignal, pyqtSlot, QThread
from PyQt5.QtGui import QIcon, QTextCursor


class MyReceiver(QObject):
    mysignal = pyqtSignal(str)

    def __init__(self,queue,*args,**kwargs):
        QObject.__init__(self,*args,**kwargs)
        self.queue = queue
        self.runCondition=True

    @pyqtSlot(str)
    def run(self):
        while self.runCondition:
            text = self.queue.get()
            self.mysignal.emit(text)

def QueueStreamSetup():

    queue = multiprocessing.Queue(-1)

    sys.stdout = WriteStream(queue)
    #sys.stderr = WriteStream(queue)

    return queue

class WriteStream(object):
    def __init__(self,queue):
        self.queue = queue

    def write(self, text):
        self.queue.put(text)

    def flush(self):
        self.queue.put('FLUSH ')
        QtTest.QTest.qWait(2 * 1000)
        pass

def threadConnect(view, queue):
    qthread = QThread()
    my_receiver = MyReceiver(queue)

    my_receiver.mysignal.connect(view.append_text)
    my_receiver.moveToThread(qthread)
    #
    qthread.started.connect(partial(my_receiver.run,))

    qthread.start()
    return(qthread, my_receiver)


class Example(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI(self)

    def restore(self):
        # Restore sys.stdout
        sys.stdout = sys.__stdout__
        sys.stderr = sys.__stderr__

    @pyqtSlot(str)
    def append_text(self,text):
        self.textEdit.moveCursor(QTextCursor.End)
        self.textEdit.insertPlainText( text )
        self.textEdit.moveCursor(QTextCursor.End)

    def initUI(self, MainWindow):
        # centralwidget
        MainWindow.resize(346, 193)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        # The Action to quit
        self.exitAction = QAction(QIcon('exit24.png'), 'Exit', self)
        self.exitAction.setShortcut('Ctrl+Q')
        self.exitAction.triggered.connect(self.close)

        # The bar
        self.statusBar()
        self.menubar = self.menuBar()
        self.fileMenu = self.menubar.addMenu('&File')
        self.exitMenu=self.fileMenu.addAction(self.exitAction)

        # tThe Button
        self.btn_quit = QtWidgets.QPushButton(self.centralwidget)
        self.btn_quit.setGeometry(QtCore.QRect(120, 20, 89, 25))
        self.btn_quit.clicked.connect(lambda: self.doPrint() )

        # The textEdit
        self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit.setGeometry(QtCore.QRect(10, 60, 321, 81))

        # Show the frame
        MainWindow.setCentralWidget(self.centralwidget)
        self.show()

    def doPrint(self):
        # Test to print something.
        print('TEST doPrint')

    def closeEvent(self, event):
        # Ask a question before to quit.
        reply = QMessageBox.question(self, 'Message',
            "Are you sure to quit?", QMessageBox.Yes |
            QMessageBox.No, QMessageBox.No)

        # Treat the answer.
        if reply == QMessageBox.Yes:
            self.restore()
            event.accept()
        else:
            event.ignore()

def main():
    queue = QueueStreamSetup()
    app = QApplication(sys.argv)
    ex = Example()
    qthread, my_receiver = threadConnect(ex, queue)

    return app, ex, queue, qthread, my_receiver

def finish(queue, qthread, my_receiver):
    print('Finish')
    my_receiver.runCondition=False
    queue.close()
    queue.join_thread()
    qthread.terminate()
    qthread.wait()
    qthread.exit()
    print('Finish Done')

if __name__ == '__main__':

    app, ex, queue, qthread, my_receiver =main()
    rc= app.exec_()
    finish(queue, qthread, my_receiver)

    print('the application ends with exit code {}'.format(rc))
    sys.exit(rc)

然后是名為 test_GUI.py 的 pytest 文件:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os, sys
import pytest

from PyQt5 import QtGui, QtCore, QtWidgets, QtTest
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QCoreApplication, Qt, QObject

import GUI

@pytest.fixture(scope="module")
def Viewer(request):
    print("  SETUP GUI")
    yield GUI.main()
    print("  TEARDOWN GUI")

class Test_GUI_CXS() :

    def test_launching(self, Viewer, qtbot, mocker, caplog):
        # open the window and add the qtbot.
        print("  SETUP Window")
        app, window, queue, qthread, my_receiver = Viewer
        qtbot.addWidget(window)
        qtbot.wait_for_window_shown(window)
        QtTest.QTest.qWait(0.5 *1000)

        # Test
        qtbot.mouseClick( window.btn_quit, QtCore.Qt.LeftButton )
        QtTest.QTest.qWait(0.5 *1000)

        # EXIT
        mocker.patch.object(QMessageBox, 'question', return_value=QMessageBox.Yes)
        window.exitAction.trigger()
        QtTest.QTest.qWait(1 *1000)
        assert window.close()

        # Finish the processes.
        print( "App END")
        GUI.finish(queue, qthread, my_receiver)
        print( "END TEST.")

因此,如果我運行命令: pytest -v -s ./test_GUI.py ,我會在消息中得到以下元素:

Qt exceptions in virtual methods:
________________________________________________________________________________
Traceback (most recent call last):
  File "xxx/GUI.py", line 25, in run
   text = self.queue.get()

File "xxx/lib/python3.7/multiprocessing/connection.py", line 383, in _recv
    raise EOFError
EOFError

我不明白為什么會出現此文件結尾錯誤,但我認為它與QueueQthread的終止有關。 因為在Queue不為空之前, my_receiver不能停止運行,所以Qthread不能被終止。 不幸的是,我在 Internet 上沒有發現有關此類問題的信息。

對此案的任何建議或幫助將不勝感激。

問題是當你關閉隊列時self.queue.get()仍在運行,阻止線程完成執行,因為它while self.runCondition:執行while self.runCondition: 考慮到上述情況,一個可能的解決方案是發送一個 None 然后關閉隊列:

class MyReceiver(QObject):
    mysignal = pyqtSignal(str)

    def __init__(self, queue, *args, **kwargs):
        super(MyReceiver, self).__init__(*args, **kwargs)
        self.queue = queue
        self.runCondition = True

    def run(self):
        while self.runCondition:
            text = self.queue.get()
            if isinstance(text, str):
                self.mysignal.emit(text)
def finish(queue, qthread, my_receiver):
    print('Finish')
    my_receiver.runCondition = False
    queue.put(None)
    qthread.quit()
    qthread.wait()
    qthread.exit()
    queue.close()
    queue.join_thread()
    print('Finish Done')

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM