Add tests, fix shutdown
This commit is contained in:
parent
746f3e4121
commit
b870140608
@ -24,8 +24,6 @@ static int PyNymeaLoggingHandler_init(PyNymeaLoggingHandler */*self*/, PyObject
|
|||||||
static void PyNymeaLoggingHandler_dealloc(PyNymeaLoggingHandler * self)
|
static void PyNymeaLoggingHandler_dealloc(PyNymeaLoggingHandler * self)
|
||||||
// destruct the object
|
// destruct the object
|
||||||
{
|
{
|
||||||
// FIXME: Why is this not called? Seems we're leaking...
|
|
||||||
Q_ASSERT(false);
|
|
||||||
Py_TYPE(self)->tp_free(self);
|
Py_TYPE(self)->tp_free(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -73,7 +73,6 @@ static void PyThing_setThing(PyThing *self, Thing *thing)
|
|||||||
|
|
||||||
self->pyStates = PyList_New(thing->states().count());
|
self->pyStates = PyList_New(thing->states().count());
|
||||||
for (int i = 0; i < thing->states().count(); i++) {
|
for (int i = 0; i < thing->states().count(); i++) {
|
||||||
qWarning() << "i" << i;
|
|
||||||
State state = thing->states().at(i);
|
State state = thing->states().at(i);
|
||||||
PyObject *pyState = Py_BuildValue("{s:s, s:O}",
|
PyObject *pyState = Py_BuildValue("{s:s, s:O}",
|
||||||
"stateTypeId", state.stateTypeId().toString().toUtf8().data(),
|
"stateTypeId", state.stateTypeId().toString().toUtf8().data(),
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
#include <QtConcurrent/QtConcurrentRun>
|
#include <QtConcurrent/QtConcurrentRun>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
#include <QFuture>
|
||||||
|
|
||||||
PyThreadState* PythonIntegrationPlugin::s_mainThread = nullptr;
|
PyThreadState* PythonIntegrationPlugin::s_mainThread = nullptr;
|
||||||
PyObject* PythonIntegrationPlugin::s_nymeaModule = nullptr;
|
PyObject* PythonIntegrationPlugin::s_nymeaModule = nullptr;
|
||||||
@ -71,7 +72,6 @@ static PyModuleDef nymea_module =
|
|||||||
"nymea module for python based integration plugins", // const char* m_doc;
|
"nymea module for python based integration plugins", // const char* m_doc;
|
||||||
-1, // Py_ssize_t m_size;
|
-1, // Py_ssize_t m_size;
|
||||||
nymea_methods, // PyMethodDef *m_methods
|
nymea_methods, // PyMethodDef *m_methods
|
||||||
// inquiry m_reload; traverseproc m_traverse; inquiry m_clear; freefunc m_free;
|
|
||||||
nullptr, nullptr, nullptr, nullptr
|
nullptr, nullptr, nullptr, nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -274,9 +274,16 @@ PythonIntegrationPlugin::PythonIntegrationPlugin(QObject *parent) : IntegrationP
|
|||||||
|
|
||||||
PythonIntegrationPlugin::~PythonIntegrationPlugin()
|
PythonIntegrationPlugin::~PythonIntegrationPlugin()
|
||||||
{
|
{
|
||||||
PyGILState_STATE s = PyGILState_Ensure();
|
PyGILState_Ensure();
|
||||||
|
|
||||||
|
while (!m_runningThreads.isEmpty()) {
|
||||||
|
PyObject *loop = m_runningThreads.keys().first();
|
||||||
|
PyObject *stop = PyObject_GetAttrString(loop, "stop");
|
||||||
|
PyObject_CallFunctionObjArgs(stop, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
Py_XDECREF(s_plugins.take(this));
|
Py_XDECREF(s_plugins.take(this));
|
||||||
PyGILState_Release(s);
|
Py_FinalizeEx();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PythonIntegrationPlugin::initPython()
|
void PythonIntegrationPlugin::initPython()
|
||||||
@ -723,7 +730,7 @@ bool PythonIntegrationPlugin::callPluginFunction(const QString &function, PyObje
|
|||||||
dumpError();
|
dumpError();
|
||||||
|
|
||||||
PyObject *run_until_complete = PyObject_GetAttrString(loop, "run_until_complete");
|
PyObject *run_until_complete = PyObject_GetAttrString(loop, "run_until_complete");
|
||||||
QtConcurrent::run([run_until_complete, task, loop, result](){
|
QFuture<void> future = QtConcurrent::run([this, run_until_complete, task, loop, result](){
|
||||||
PyGILState_STATE g = PyGILState_Ensure();
|
PyGILState_STATE g = PyGILState_Ensure();
|
||||||
// auto s = PyThreadState_New(PyInterpreterState_Main());
|
// auto s = PyThreadState_New(PyInterpreterState_Main());
|
||||||
// PyThreadState *previousThreadState = PyThreadState_Swap(s);
|
// PyThreadState *previousThreadState = PyThreadState_Swap(s);
|
||||||
@ -732,11 +739,13 @@ bool PythonIntegrationPlugin::callPluginFunction(const QString &function, PyObje
|
|||||||
Py_DECREF(task);
|
Py_DECREF(task);
|
||||||
Py_DECREF(run_until_complete);
|
Py_DECREF(run_until_complete);
|
||||||
Py_DECREF(result);
|
Py_DECREF(result);
|
||||||
|
m_runningThreads.remove(loop);
|
||||||
// PyThreadState_Swap(previousThreadState);
|
// PyThreadState_Swap(previousThreadState);
|
||||||
// PyThreadState_Clear(s);
|
// PyThreadState_Clear(s);
|
||||||
// PyThreadState_Delete(s);
|
// PyThreadState_Delete(s);
|
||||||
PyGILState_Release(g);
|
PyGILState_Release(g);
|
||||||
});
|
});
|
||||||
|
m_runningThreads.insert(loop, future);
|
||||||
|
|
||||||
Py_DECREF(create_task);
|
Py_DECREF(create_task);
|
||||||
Py_DECREF(add_done_callback);
|
Py_DECREF(add_done_callback);
|
||||||
|
|||||||
@ -82,6 +82,8 @@ private:
|
|||||||
|
|
||||||
// Need to keep a copy of plugin params and sync that in a thread-safe manner
|
// Need to keep a copy of plugin params and sync that in a thread-safe manner
|
||||||
ParamList m_pluginConfigCopy;
|
ParamList m_pluginConfigCopy;
|
||||||
|
|
||||||
|
QHash<PyObject*, QFuture<void>> m_runningThreads;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PYTHONINTEGRATIONPLUGIN_H
|
#endif // PYTHONINTEGRATIONPLUGIN_H
|
||||||
|
|||||||
@ -89,7 +89,7 @@ async def setupThing(info):
|
|||||||
|
|
||||||
|
|
||||||
async def postSetupThing(thing):
|
async def postSetupThing(thing):
|
||||||
logger.log("postSetupThing for", thing.name, thing.params[0].value)
|
logger.log("postSetupThing for", thing.name)
|
||||||
thing.nameChangedHandler = lambda thing : logger.log("Thing name changed", thing.name)
|
thing.nameChangedHandler = lambda thing : logger.log("Thing name changed", thing.name)
|
||||||
|
|
||||||
if thing.thingClassId == pyMockAutoThingClassId:
|
if thing.thingClassId == pyMockAutoThingClassId:
|
||||||
@ -111,3 +111,8 @@ def autoThings():
|
|||||||
if thing.thingClassId == pyMockAutoThingClassId:
|
if thing.thingClassId == pyMockAutoThingClassId:
|
||||||
autoThings.append(thing)
|
autoThings.append(thing)
|
||||||
return autoThings
|
return autoThings
|
||||||
|
|
||||||
|
|
||||||
|
# Intentionally commented out to also have a test case for unimplmented functions
|
||||||
|
# def thingRemoved(thing):
|
||||||
|
# logger.log("thingRemoved for", thing.name)
|
||||||
|
|||||||
@ -13,6 +13,7 @@ SUBDIRS = \
|
|||||||
loggingloading \
|
loggingloading \
|
||||||
mqttbroker \
|
mqttbroker \
|
||||||
plugins \
|
plugins \
|
||||||
|
pythonplugins \
|
||||||
rules \
|
rules \
|
||||||
scripts \
|
scripts \
|
||||||
states \
|
states \
|
||||||
|
|||||||
6
tests/auto/pythonplugins/pythonplugins.pro
Normal file
6
tests/auto/pythonplugins/pythonplugins.pro
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
TARGET = testactions
|
||||||
|
|
||||||
|
include(../../../nymea.pri)
|
||||||
|
include(../autotests.pri)
|
||||||
|
|
||||||
|
SOURCES += testpythonplugins.cpp
|
||||||
140
tests/auto/pythonplugins/testpythonplugins.cpp
Normal file
140
tests/auto/pythonplugins/testpythonplugins.cpp
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
*
|
||||||
|
* Copyright 2013 - 2020, nymea GmbH
|
||||||
|
* Contact: contact@nymea.io
|
||||||
|
*
|
||||||
|
* This file is part of nymea.
|
||||||
|
* This project including source code and documentation is protected by
|
||||||
|
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||||
|
* reproduction, publication, editing and translation, are reserved. The use of
|
||||||
|
* this project is subject to the terms of a license agreement to be concluded
|
||||||
|
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||||
|
* under https://nymea.io/license
|
||||||
|
*
|
||||||
|
* GNU General Public License Usage
|
||||||
|
* Alternatively, this project may be redistributed and/or modified under the
|
||||||
|
* terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, GNU version 3. This project is distributed in the hope that it
|
||||||
|
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
* Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this project. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* For any further details and any questions please contact us under
|
||||||
|
* contact@nymea.io or see our FAQ/Licensing Information on
|
||||||
|
* https://nymea.io/license/faq
|
||||||
|
*
|
||||||
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||||
|
|
||||||
|
#include "nymeatestbase.h"
|
||||||
|
|
||||||
|
#include "integrations/thing.h"
|
||||||
|
|
||||||
|
ThingClassId pyMockThingClassId = ThingClassId("1761c256-99b1-41bd-988a-a76087f6a4f1");
|
||||||
|
ThingClassId pyMockDiscoveryPairingThingClassId = ThingClassId("248c5046-847b-44d0-ab7c-684ff79197dc");
|
||||||
|
ParamTypeId pyMockDiscoveryPairingResultCountDiscoveryParamTypeID = ParamTypeId("ef5f6b90-e9d8-4e77-a14d-6725cfb07116");
|
||||||
|
|
||||||
|
using namespace nymeaserver;
|
||||||
|
|
||||||
|
class TestPythonPlugins: public NymeaTestBase
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline void verifyThingError(const QVariant &response, Thing::ThingError error = Thing::ThingErrorNoError) {
|
||||||
|
verifyError(response, "thingError", enumValueName(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
|
||||||
|
void initTestCase();
|
||||||
|
|
||||||
|
void setupAndRemoveThing();
|
||||||
|
void testDiscoverPairAndRemoveThing();
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void TestPythonPlugins::initTestCase()
|
||||||
|
{
|
||||||
|
NymeaTestBase::initTestCase();
|
||||||
|
QLoggingCategory::setFilterRules("*.debug=false\n"
|
||||||
|
"Tests.debug=true\n"
|
||||||
|
"PyMock.debug=true\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestPythonPlugins::setupAndRemoveThing()
|
||||||
|
{
|
||||||
|
QVariantMap resultCountParam;
|
||||||
|
resultCountParam.insert("paramTypeId", pyMockDiscoveryPairingResultCountDiscoveryParamTypeID);
|
||||||
|
resultCountParam.insert("value", 2);
|
||||||
|
|
||||||
|
QVariantList discoveryParams;
|
||||||
|
discoveryParams.append(resultCountParam);
|
||||||
|
|
||||||
|
QVariantMap params;
|
||||||
|
params.insert("thingClassId", pyMockThingClassId);
|
||||||
|
params.insert("name", "Py test thing");
|
||||||
|
QVariant response = injectAndWait("Integrations.AddThing", params);
|
||||||
|
|
||||||
|
verifyThingError(response, Thing::ThingErrorNoError);
|
||||||
|
ThingId thingId = response.toMap().value("params").toMap().value("thingId").toUuid();
|
||||||
|
qCDebug(dcTests()) << "New thing id" << thingId;
|
||||||
|
|
||||||
|
params.clear();
|
||||||
|
params.insert("thingId", thingId);
|
||||||
|
injectAndWait("Integrations.RemoveThing", params);
|
||||||
|
verifyThingError(response, Thing::ThingErrorNoError);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestPythonPlugins::testDiscoverPairAndRemoveThing()
|
||||||
|
{
|
||||||
|
// Discover
|
||||||
|
QVariantMap resultCountParam;
|
||||||
|
resultCountParam.insert("paramTypeId", pyMockDiscoveryPairingResultCountDiscoveryParamTypeID);
|
||||||
|
resultCountParam.insert("value", 2);
|
||||||
|
|
||||||
|
QVariantList discoveryParams;
|
||||||
|
discoveryParams.append(resultCountParam);
|
||||||
|
|
||||||
|
QVariantMap params;
|
||||||
|
params.insert("thingClassId", pyMockDiscoveryPairingThingClassId);
|
||||||
|
params.insert("discoveryParams", discoveryParams);
|
||||||
|
QVariant response = injectAndWait("Integrations.DiscoverThings", params);
|
||||||
|
|
||||||
|
verifyThingError(response, Thing::ThingErrorNoError);
|
||||||
|
QCOMPARE(response.toMap().value("params").toMap().value("thingDescriptors").toList().count(), 2);
|
||||||
|
|
||||||
|
ThingDescriptorId descriptorId = response.toMap().value("params").toMap().value("thingDescriptors").toList().first().toMap().value("id").toUuid();
|
||||||
|
|
||||||
|
// Pair
|
||||||
|
params.clear();
|
||||||
|
params.insert("thingDescriptorId", descriptorId);
|
||||||
|
response = injectAndWait("Integrations.PairThing", params);
|
||||||
|
verifyThingError(response, Thing::ThingErrorNoError);
|
||||||
|
|
||||||
|
qWarning() << "respo" << response.toMap().value("params").toMap();
|
||||||
|
PairingTransactionId transactionId = response.toMap().value("params").toMap().value("pairingTransactionId").toUuid();
|
||||||
|
qWarning() << "transactionId" << transactionId;
|
||||||
|
|
||||||
|
params.clear();
|
||||||
|
params.insert("pairingTransactionId", transactionId);
|
||||||
|
params.insert("username", "john");
|
||||||
|
params.insert("secret", "smith");
|
||||||
|
response = injectAndWait("Integrations.ConfirmPairing", params);
|
||||||
|
verifyThingError(response, Thing::ThingErrorNoError);
|
||||||
|
ThingId thingId = response.toMap().value("params").toMap().value("thingId").toUuid();
|
||||||
|
|
||||||
|
// Remove
|
||||||
|
params.clear();
|
||||||
|
params.insert("thingId", thingId);
|
||||||
|
response = injectAndWait("Integrations.RemoveThing", params);
|
||||||
|
verifyThingError(response, Thing::ThingErrorNoError);
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "testpythonplugins.moc"
|
||||||
|
QTEST_MAIN(TestPythonPlugins)
|
||||||
Reference in New Issue
Block a user