Add some tests for widgets.

This commit is contained in:
Mikaël Capelle
2022-04-29 22:27:31 +02:00
parent 051c0589b5
commit 225c86d258
8 changed files with 216 additions and 51 deletions
-47
View File
@@ -114,53 +114,6 @@ PYBIND11_MODULE(mobase, m)
}
});
py::detail::type_caster<QString> t1;
py::detail::type_caster<QVariant> t2;
m.def("testQStringList", [](QStringList const& list) {
QStringList res = list;
for (QString& value : res) {
value = value + "_CPP";
}
return res;
});
m.def("testGuessedString", [](GuessedValue<QString> const& value) {
return std::make_tuple(value.operator const QString&(), value.variants());
});
m.def("testQStringList", [](QStringList const& list) {
QStringList res = list;
for (QString& value : res) {
value = value + "_CPP";
}
return res;
});
m.def("testQMap1", [](QMap<QString, QString> const& map) {
QMap<QString, int> res;
for (auto it = map.begin(); it != map.end(); ++it) {
res[it.key()] = it.value().size();
}
return res;
});
m.def("testQMap2", [](QMap<QString, int> const& map) {
QMap<QString, QString> res;
for (auto it = map.begin(); it != map.end(); ++it) {
res[it.key()] = QString::number(it.value());
}
return res;
});
m.def("testDateTime1", []() {
return QDateTime::fromString("2022-02-15T12:33:45", Qt::ISODate);
});
m.def("testDateTime2", [](QDateTime const& datetime) {
return datetime.toString();
});
m.def("testEnum0", []() {
return Qt::GlobalColor::darkRed;
});
@@ -174,8 +174,7 @@ namespace pybind11::detail::qt {
return Py_None;
}
if (policy == return_value_policy::take_ownership &&
std::is_copy_constructible_v<QClass>) {
if (policy == return_value_policy::take_ownership) {
// ensure Python deletes the C++ component
qt::sipAPI()->api_transfer_back(sipObj);
}
+2 -2
View File
@@ -8,7 +8,7 @@ else()
set(UIBASE_PATH "${MO2_INSTALL_PATH}/bin/uibase.dll")
endif()
add_custom_target(pytest EXCLUDE_FROM_ALL
add_custom_target(pytest
${CMAKE_COMMAND} -E env
PYTHONPATH="${PYLIB_DIR}\\;$<TARGET_FILE_DIR:mobase>"
"UIBASE_PATH=${UIBASE_PATH}" "QT_ROOT=${QT_ROOT}"
@@ -22,7 +22,7 @@ mo2_python_pip_install(pytest
add_dependencies(pytest mobase)
file(GLOB test_files "test_*.cpp")
file(GLOB test_files CONFIGURE_DEPENDS "test_*.cpp")
foreach (test_file ${test_files})
get_filename_component(target ${test_file} NAME_WLE)
string(REPLACE "test_" "" pymodule ${target})
+9
View File
@@ -1,7 +1,16 @@
# -*- encoding: utf-8 -*-
import os
import sys
from pathlib import Path
def pytest_configure():
global app
os.add_dll_directory(Path(os.getenv("QT_ROOT")).joinpath("bin"))
os.add_dll_directory(Path(os.getenv("UIBASE_PATH")).parent)
from PyQt6.QtWidgets import QApplication
app = QApplication(sys.argv)
+27
View File
@@ -2,6 +2,8 @@
#include <pybind11/pybind11.h>
using namespace pybind11::literals;
PYBIND11_MODULE(qt, m)
{
// QString
@@ -29,4 +31,29 @@ PYBIND11_MODULE(qt, m)
m.def("qstringlist_at", [](QStringList const& values, int index) {
return values.at(index);
});
// QMap
m.def("qmap_to_length", [](QMap<QString, QString> const& map) {
QMap<QString, int> res;
for (auto it = map.begin(); it != map.end(); ++it) {
res[it.key()] = it.value().size();
}
return res;
});
// QDateTime
m.def(
"datetime_from_string",
[](QString const& date, Qt::DateFormat format) {
return QDateTime::fromString(date, format);
},
"string"_a, "format"_a = Qt::DateFormat::ISODate);
m.def(
"datetime_to_string",
[](QDateTime const& datetime, Qt::DateFormat format) {
return datetime.toString(format);
},
"datetime"_a, "format"_a = Qt::DateFormat::ISODate);
}
+27
View File
@@ -1,4 +1,6 @@
import pytest
from PyQt6.QtCore import QDateTime, Qt
from PyQt6.QtWidgets import QWidget
m = pytest.importorskip("mobase_tests.qt")
@@ -21,3 +23,28 @@ def test_qstringlist():
assert m.qstringlist_at(["x", "y"], 0) == "x"
assert m.qstringlist_at(["x", "y"], 1) == "y"
def test_qmap():
assert m.qmap_to_length({"t1": "abc", "t2": "o", "t3": ""}) == {
"t1": 3,
"t2": 1,
"t3": 0,
}
def test_qdatetime():
assert m.datetime_from_string("2022-03-01") == QDateTime(2022, 3, 1, 0, 0)
date = QDateTime(1995, 5, 20, 0, 0)
assert (
m.datetime_from_string(
date.toString(Qt.DateFormat.TextDate), Qt.DateFormat.TextDate
)
== date
)
assert m.datetime_to_string(date) == "1995-05-20T00:00:00"
assert m.datetime_to_string(date, Qt.DateFormat.TextDate) == date.toString(
Qt.DateFormat.TextDate
)
+87
View File
@@ -0,0 +1,87 @@
#include "pybind11_qt/pybind11_qt.h"
#include <pybind11/pybind11.h>
#include <QMap>
#include <QWidget>
#include <iostream>
namespace py = pybind11;
using namespace pybind11::literals;
QMap<QString, QWidget*> s_Widgets;
QWidget* s_Parent;
class CustomWidget : public QWidget {
public:
CustomWidget(QString const& name, QWidget* parent = nullptr) : QWidget(parent)
{
s_Widgets[name] = this;
setProperty("name", name);
}
~CustomWidget() { s_Widgets.remove(property("name").toString()); }
};
class PyCustomWidget : public CustomWidget {
public:
using CustomWidget::CustomWidget;
int heightForWidth(int value) const
{
PYBIND11_OVERRIDE(int, CustomWidget, heightForWidth, value);
}
};
PYBIND11_MODULE(qt_widgets, m)
{
s_Parent = new QWidget();
py::class_<CustomWidget, PyCustomWidget, py::qt::qholder<CustomWidget>>
pyCustomWidget(m, "CustomWidget");
pyCustomWidget
.def(py::init<QString, QWidget*>(), "name"_a, "parent"_a = (QWidget*)nullptr)
.def("set_parent_cpp", [](CustomWidget* w) {
w->setParent(s_Parent);
});
py::qt::add_qt_delegate<QWidget>(pyCustomWidget, "_widget");
m.def("is_alive", [](QString const& name) {
return s_Widgets.contains(name);
});
m.def("get", [](QString const& name) {
return s_Widgets.contains(name) ? s_Widgets[name] : nullptr;
});
m.def("set_parent", [](QWidget* widget) {
widget->setParent(s_Parent);
});
m.def("is_owned_cpp", [](QString const& name) {
return s_Widgets.contains(name) && s_Widgets[name]->parent() == s_Parent;
});
m.def("make_widget_own_cpp", [](QString const& name) -> QWidget* {
return new CustomWidget(name, s_Parent);
});
m.def(
"make_widget_own_py",
[](QString const& name) -> QWidget* {
return new CustomWidget(name);
},
py::return_value_policy::take_ownership);
// simply passing the widget gives the ownership of the Python object to C++
m.def("send_to_cpp", [](QString const& name, QWidget* widget) {
widget->setProperty("name", name);
widget->setParent(s_Parent);
s_Widgets[name] = widget;
});
m.def("heightForWidth", [](QString const& name, int value) {
return s_Widgets.contains(name) ? s_Widgets[name]->heightForWidth(value)
: -1024;
});
}
+63
View File
@@ -0,0 +1,63 @@
import pytest
from PyQt6.QtWidgets import QWidget
m = pytest.importorskip("mobase_tests.qt_widgets")
class PyWidget(QWidget):
def heightForWidth(self, value: int) -> int:
return value * 3 + 4
class PyCustomWidget(m.CustomWidget):
def __init__(self, name: str):
super().__init__(name)
def heightForWidth(self, value: int) -> int:
return value * 6 - 5
def test_qt_widget():
# own cpp
w = m.make_widget_own_cpp("w1")
assert m.is_alive("w1")
assert m.is_owned_cpp("w1")
del w
assert m.is_alive("w1")
# own py
w = m.make_widget_own_py("w2")
assert m.is_alive("w2")
assert not m.is_owned_cpp("w2")
del w
assert not m.is_alive("w2")
# transfer to C++
w = PyWidget()
m.send_to_cpp("w3", w)
# delete the reference w - this should NOT delete the underlying object since it
# was transferred to C++
del w
assert m.is_alive("w3")
assert m.is_owned_cpp("w3")
# if the Python object is dead (BAD!), this will crash horrible
assert m.heightForWidth("w3", 4) == 4 * 3 + 4
# CustomWidget as a qholder, so the construction itself transfers the ownership
# to C++
w = PyCustomWidget("w4")
w.set_parent_cpp()
assert m.is_alive("w4")
assert m.heightForWidth("w4", 7) == 6 * 7 - 5
assert w.heightForWidth(7) == 6 * 7 - 5
# can call function not defined and not bound through the delegate
assert not w.hasHeightForWidth()
del w
assert m.is_alive("w4")
assert m.heightForWidth("w4", 7) == 6 * 7 - 5