When running a test that launches a dialog, the test is unable to programmatically click a button on the dialog. If I manually click the button, the test completes. Also I'm unsure why the dialog even shows at all (since I don't see the original window it was launched from).
Python 3.7.16, pytest 7.2.1
foo.py
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Foo(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.setupUi()
def setupUi(self):
self.resize(400, 200)
self.button = QtWidgets.QPushButton("ClickMe")
self.button.clicked.connect(self.launchDialog)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.button)
self.setLayout(layout)
def launchDialog(self):
self.dialog = FooDialog(parent=self)
self.dialog.exec()
class FooDialog(QtWidgets.QMessageBox):
def __init__(self, parent=None):
super(FooDialog, self).__init__(parent)
self.setWindowTitle("foo.FooDialog")
self.button = QtWidgets.QPushButton("ClickMe")
self.addButton(self.button, QtWidgets.QMessageBox.ButtonRole.ActionRole)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
foo = Foo()
foo.show()
sys.exit(app.exec_())
test_foo.py
import pytest, time
from pytestqt.qtbot import QtBot
from PyQt5 import QtCore
from foo import Foo
#pytest.fixture
def app(qtbot):
foo = Foo()
qtbot.addWidget(foo)
return foo
def test_button(app):
qtbot = QtBot(app)
qtbot.mouseClick(app.button, QtCore.Qt.MouseButton.LeftButton, delay=0)
time.sleep(2)
qtbot.mouseClick(app.dialog.button, QtCore.Qt.MouseButton.LeftButton, delay=0)
I run the test thus:
pytest -s test_foo.py
I ran the test and expected it to complete, but instead the test hangs waiting for the button on the dialog to be clicked.
I worked around the problem by mocking the dialog
Related
I can't make the button work, it works as it should on the windows, why doesn't it work in ubuntu?
import tkinter as tk
from tkinter import ttk
import sys
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.attributes("-alpha", 1)
self.attributes("-topmost", True)
self.overrideredirect(True)
self.resizable(False, False)
self.title("CPU-RAM usage monitor bar")
self.set_ui()
def set_ui(self):
exit_but = tk.Button(self, text="Exit", command=lambda: self.app_exit)
exit_but.pack(fill=tk.X)
def app_exit(self):
self.destroy()
sys.exit()
root = Application()
root.mainloop()
I am trying to write applications with PyGObject. I have set the app name using GLib but it seems to be broken. There is no problem with other system programs that use Turkish characters in their titles.
import sys
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, GLib
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_default_size(300, 400)
self.set_title("Merhaba dünya!")
GLib.set_application_name("Merhaba Dünya!")
GLib.set_prgname('Merhaba Dünya!')
self.main_box = Gtk.Box(
orientation = Gtk.Orientation.VERTICAL
)
self.set_child(self.main_box)
self.lbl_hello = Gtk.Label(
label = "Merhaba dünya"
)
self.main_box.append(self.lbl_hello)
self.btn_hello = Gtk.Button(
label = "Bana tıkla"
)
self.btn_hello.connect(
"clicked",
self.btn_hello_clicked
)
self.main_box.append(self.btn_hello)
def btn_hello_clicked(self, button):
print("Merhaba dünya!")
class MyApp(Adw.Application):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.connect('activate', self.on_activate)
def on_activate(self, app):
self.win = MainWindow(application=app)
self.win.present()
app = MyApp(application_id="net.teteos.example")
app.run(sys.argv)
I have a simple test PyQt4 application where i'm using a QProcess to start a program (linux) and print the stdout. When I close the PyQt4 application the child process (started by QProcess) keeps running, which is what I want.
However when I run the same example using PySide2 instead (switching QtGui to QtWidgets etc) and I then close the PySide2 application, the child process also closes..
Is this a bug with either PyQt4 or PySide2? And is there a way to get PySide2 to have the same behaviour as PyQt4 in this case? I know there is startDetached on the QProcess but then I lose access to stdout so this isn't an option for me..
EDIT, added simple PySide2 example. In this case i'm opening Dolphin as the program - this isn't the program i'm opening in my real world application but it mimics the same behaviour
import sys
from PySide2 import QtWidgets,QtCore, QtWidgets
class gui(QtWidgets.QMainWindow):
def __init__(self):
super(gui, self).__init__()
self.initUI()
def stdoutReady(self, process):
data = str(self.process.readAllStandardOutput())
print('STDOUT: {}'.format(data))
def stderrorReady(self, process):
data = str(self.process.readAllStandardError())
print('STDERROR: {}'.format(data))
def processFinished(self, exitCode, exitStatus):
print('EXIT CODE: {}, EXIT STATUS: {}'.format(exitCode, exitStatus))
def initUI(self):
layout = QtWidgets.QHBoxLayout()
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
self.process = QtCore.QProcess(self)
self.process.readyReadStandardOutput.connect(lambda: self.stdoutReady(self.process))
self.process.readyReadStandardError.connect(lambda: self.stderrorReady(self.process))
self.process.start('dolphin')
def main():
app = QtWidgets.QApplication(sys.argv)
ui=gui()
ui.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
'I want to build a module containing class AnimationCanvas inside (anim.py). And I want to call this module in a separate main.py file where the data (a variable) is changing (potentially with GUI). The instance animatedAxes would automatically update its plot by taking data from the variables in main.py file, while the main.py code is running (a sort of parallel processes).'
'Problem: the instance of class AnimationCanvas from the module does not see the variables of the class main in the main.py file.
I know how to do it if the class AnimationCanvas and the class main are in the same file. However I want to have an animation module (separate file), which can be used anywhere, just by importing it and writing a couple of lines.'
'I can call __init__ function of the class AnimationCanvas and pass the variables into it, but then it is a one-time effect, and if the variables change in class main, then animatedAxes instance will not see this change.'
'Single file which works (single.py):'
import matplotlib.pyplot as plt
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D
import numpy as np
class main():
def __init__(self):
self.size=800
main.data=np.random.rand(self.size)
# initialize animated graph routine
self.animatedAxes = AnimationCanvas()
# run random data array
for ii in range(20):
main.data=np.random.rand(self.size)
plt.pause(0.1)
class AnimationCanvas(TimedAnimation):
def __init__(self):
# initialize random data array
self.data = np.random.rand(5)
# Create animation axis and figure
self.fig = plt.figure(1, figsize=(5, 5))
self.ax = plt.axes([0.1, 0.1, 0.8, 0.8])
self.line1 = Line2D([], [], color='blue')
self.ax.add_line(self.line1)
# start animation with interval of 10 milliseconds
TimedAnimation.__init__(self, self.fig, interval=10, blit=True)
def new_frame_seq(self):
return iter(range(5*5))
def _step(self, *args):
try:
TimedAnimation._step(self, *args)
except Exception as e:
TimedAnimation._stop(self)
pass
def _draw_frame(self, framedata):
# update self.data
self.data=main.data
# update plot with self.data
self.line1.set_data(np.arange(len(self.data)),self.data)
if __name__ == '__main__':
main()
'Two files which do not work:'
'main.py:'
import matplotlib.pyplot as plt
import numpy as np
from anim import AnimationCanvas
class main():
def __init__(self):
self.size=800
self.data=np.random.rand(self.size)
# initialize animated graph routine
self.animatedAxes = AnimationCanvas()
# run random data array
for ii in range(20):
print(ii)
self.data=np.random.rand(self.size)
plt.pause(0.1)
if __name__ == '__main__':
main()
'anim.py:'
import matplotlib.pyplot as plt
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D
import numpy as np
class AnimationCanvas(TimedAnimation):
def __init__(self):
# initialize random data array
self.data = np.random.rand(5)
# Create animation axis and figure
self.fig = plt.figure(1, figsize=(5, 5))
self.ax = plt.axes([0.1, 0.1, 0.8, 0.8])
self.line1 = Line2D([], [], color='blue')
self.ax.add_line(self.line1)
# start animation with interval of 10 milliseconds
TimedAnimation.__init__(self, self.fig, interval=10, blit=True)
def new_frame_seq(self):
return iter(range(5*5))
def _step(self, *args):
try:
TimedAnimation._step(self, *args)
except Exception as e:
TimedAnimation._stop(self)
pass
def _draw_frame(self, framedata):
'update self.data:'
'????????????????'
'update plot with self.data'
self.line1.set_data(np.arange(len(self.data)),self.data)
'I tried to use super(AnimationCanvas,self).__init__() but it does not work.'
'In my understanding I need a direct connection between self of the class main and self of class AnimationCanvas. Any suggestions are appreciated. Thanks.'
I've just run into an issue running unittests on my flask app after I had roughly 100 unittests. All unittests will pass, but when run all at once they will fail with the following error:
OperationalError: (OperationalError) FATAL: remaining connection slots are reserved for non-replication superuser connections
Everything is running in a virtualbox/vagrant/ubuntu12.04 instance on local machine. My postgres max_connections is set to 100 so I'm assuming that the connections aren't closing and after running 100 tests I use up all the available ones.
This person Flask unit tests with SQLAlchemy and PostgreSQL exhausts db connections looks like they are having the same exact problem. Mike/Zzzeek (sqlalchemy dev) even responded to it saying that something may be happening in create_app() so I've included that as well below.
Does this mean I'm not closing my connections somewhere? All of these errors are triggered by db.create_all() in my setUp() method of my unittest.
# test.py
class TestCase(DataMixin, Base):
"""Base test class"""
def create_app(self):
return create_app(TestConfig())
def setUp(self):
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
# app.py
def create_app(config=None):
app = Flask(__name__)
# Config
app.config.from_object(BaseConfig())
if config is not None:
app.config.from_object(config)
# Extensions
db.init_app(app)
mail.init_app(app)
bcrypt.init_app(app)
# Blueprints
app.register_blueprint(core_blueprint, url_prefix='/')
app.register_blueprint(accounts_blueprint, url_prefix='/account')
app.register_blueprint(admin_blueprint, url_prefix='/admin')
app.register_blueprint(cart_blueprint, url_prefix='/cart')
# Login Manager
login_manager.setup_app(app, add_context_processor=True)
login_manager.login_view = "accounts.login"
login_manager.user_callback = load_user
# Templates
app.jinja_env.globals['is_admin'] = is_admin
app.jinja_env.globals['is_staff'] = is_staff
#app.context_processor
def inject_cart():
cart = count = None
if current_user.is_authenticated():
cart = current_user.get_cart()
return dict(cart=cart)
# Error Handling
#app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'), 404
return app
UPDATE: Tested and fixed
Instead of making a new connection and re-creating your database every time (slow), you can use subsessions and do a rollback after each test.
The connection are reused, so this also fix the problem you're having.
class TestCase(Base):
#classmethod
def setUpClass(cls):
cls.app = create_app(MyConfig())
cls.client = cls.app.test_client()
cls._ctx = cls.app.test_request_context()
cls._ctx.push()
db.create_all()
#classmethod
def tearDownClass(cls):
db.session.remove()
db.drop_all()
db.get_engine(cls.app).dispose()
def setUp(self):
self._ctx = self.app.test_request_context()
self._ctx.push()
db.session.begin(subtransactions=True)
def tearDown(self):
db.session.rollback()
db.session.close()
self._ctx.pop()
If you need to also make an instance of the application for each test, just add it to the setUp method but leave it also in setUpClass.
Full test example below requires flask_sqlalchemy and psycopg2. Create a test database named "test" and set its connection limit to 15.
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from unittest import TestCase as Base
db = SQLAlchemy()
def create_app(config=None):
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
return app
class MyConfig(object):
SQLALCHEMY_DATABASE_URI = "postgresql://localhost/test"
TESTING = True
class TestCase(Base):
#classmethod
def setUpClass(cls):
cls.app = create_app(MyConfig())
cls.client = cls.app.test_client()
cls._ctx = cls.app.test_request_context()
cls._ctx.push()
db.create_all()
#classmethod
def tearDownClass(cls):
db.session.remove()
db.drop_all()
def setUp(self):
self._ctx = self.app.test_request_context()
self._ctx.push()
db.session.begin(subtransactions=True)
def tearDown(self):
db.session.rollback()
db.session.close()
self._ctx.pop()
class TestModel(TestCase):
def test_01(self):
pass
def test_02(self):
pass
def test_03(self):
pass
def test_04(self):
pass
def test_05(self):
pass
def test_06(self):
pass
def test_07(self):
pass
def test_08(self):
pass
def test_09(self):
pass
def test_10(self):
pass
def test_11(self):
pass
def test_12(self):
pass
def test_13(self):
pass
def test_14(self):
pass
def test_15(self):
pass
def test_16(self):
pass
if __name__ == "__main__":
import unittest
unittest.main()
I found the answer here -- https://stackoverflow.com/a/17998485/1870623 and a great explination here -- https://stackoverflow.com/a/16390645/1870623
The solution is to add db.get_engine(self.app).dispose() to the tearDown()
class TestCase(Base):
def setUp(self):
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
db.get_engine(self.app).dispose() # This