Inial python script actor implementation

This commit is contained in:
Carl Philipp Klemm 2026-06-09 19:38:12 +02:00
parent 45676b3384
commit 3df91cc73d
331 changed files with 99399 additions and 5 deletions

View file

@ -2,6 +2,8 @@
#include "actors/actor.h"
#include "actors/timeractor.h"
#include "actors/sensoractor.h"
#include "actors/scriptactor.h"
#include "sensors/sensor.h"
class TestActor : public QObject
{
@ -131,8 +133,8 @@ private slots:
std::shared_ptr<Actor> polynomalActor = Actor::createActor("Polynomal");
QVERIFY(polynomalActor != nullptr);
std::shared_ptr<Actor> factorActor = Actor::createActor("MultiFactor");
QVERIFY(factorActor != nullptr);
std::shared_ptr<Actor> scriptActor = Actor::createActor("Script");
QVERIFY(scriptActor != nullptr);
std::shared_ptr<Actor> genericActor = Actor::createActor("Actor");
QVERIFY(genericActor != nullptr);
@ -322,6 +324,269 @@ private slots:
QVERIFY(name.contains("30"));
}
// ScriptActor tests
void testScriptActorCreation()
{
ScriptActor actor;
// Should start with empty sensors
QVERIFY(actor.getSensors().isEmpty());
// Should not watch parent item by default
QVERIFY(!actor.getWatchParentItem());
// Script path and code should be empty
QVERIFY(actor.getScriptPath().isEmpty());
QVERIFY(actor.getScriptCode().isEmpty());
}
void testScriptActorSensors()
{
ScriptActor actor;
// Add sensors
Sensor sensor1(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp1");
Sensor sensor2(Sensor::TYPE_HUMIDITY, 2, 60.0, "humidity");
actor.addSensor(sensor1);
actor.addSensor(sensor2);
QVERIFY(actor.getSensors().size() == 2);
// Adding duplicate (same type+id) should not add again
Sensor sensor1Duplicate(Sensor::TYPE_TEMPERATURE, 1, 30.0, "temp1_dup");
actor.addSensor(sensor1Duplicate);
QVERIFY(actor.getSensors().size() == 2);
// Remove sensor
actor.removeSensor(sensor1);
QVERIFY(actor.getSensors().size() == 1);
QVERIFY(actor.getSensors()[0].type == Sensor::TYPE_HUMIDITY);
// Clear sensors
actor.clearSensors();
QVERIFY(actor.getSensors().isEmpty());
}
void testScriptActorSensorMatching()
{
ScriptActor actor;
// Add a temperature sensor with id 5
Sensor watchedSensor(Sensor::TYPE_TEMPERATURE, 5, 20.0, "watched_temp");
actor.addSensor(watchedSensor);
// A different temperature sensor (id 10) should NOT match
Sensor otherTempSensor(Sensor::TYPE_TEMPERATURE, 10, 25.0, "other_temp");
QVERIFY(!(otherTempSensor.type == watchedSensor.type && otherTempSensor.id == watchedSensor.id));
// A humidity sensor with same id (5) should NOT match
Sensor sameIdHumidity(Sensor::TYPE_HUMIDITY, 5, 50.0, "humidity_same_id");
QVERIFY(!(sameIdHumidity.type == watchedSensor.type && sameIdHumidity.id == watchedSensor.id));
}
void testScriptActorWatchParentItem()
{
ScriptActor actor;
QVERIFY(!actor.getWatchParentItem());
actor.setWatchParentItem(true);
QVERIFY(actor.getWatchParentItem());
actor.setWatchParentItem(false);
QVERIFY(!actor.getWatchParentItem());
}
void testScriptActorScriptPath()
{
ScriptActor actor;
QVERIFY(actor.getScriptPath().isEmpty());
actor.setScriptPath("/path/to/script.py");
QVERIFY(actor.getScriptPath() == "/path/to/script.py");
}
void testScriptActorScriptCode()
{
ScriptActor actor;
QVERIFY(actor.getScriptCode().isEmpty());
actor.setScriptCode("return 42");
QVERIFY(actor.getScriptCode() == "return 42");
}
void testScriptActorJsonSerialization()
{
ScriptActor actor;
actor.setWatchParentItem(true);
actor.setScriptPath("/path/to/script.py");
actor.setScriptCode("return 1");
Sensor sensor1(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp1");
Sensor sensor2(Sensor::TYPE_HUMIDITY, 2, 60.0, "humidity");
actor.addSensor(sensor1);
actor.addSensor(sensor2);
QJsonObject json;
actor.store(json);
// Verify JSON contents
QVERIFY(json["Type"].toString() == "Script");
QVERIFY(json["WatchParentItem"].toBool() == true);
QVERIFY(json["ScriptPath"].toString() == "/path/to/script.py");
QVERIFY(json["ScriptCode"].toString() == "return 1");
QVERIFY(json.contains("Sensors"));
QJsonArray sensorsArray = json["Sensors"].toArray();
QVERIFY(sensorsArray.size() == 2);
// Check first sensor
QVERIFY(sensorsArray[0].toObject()["Type"].toInt() == Sensor::TYPE_TEMPERATURE);
QVERIFY(sensorsArray[0].toObject()["Id"].toInteger() == 1);
}
void testScriptActorJsonDeserialization()
{
QJsonObject json;
json["Type"] = "Script";
json["WatchParentItem"] = true;
json["ScriptPath"] = "/custom/script.py";
json["ScriptCode"] = "return 99";
json["Active"] = true;
json["Exausted"] = false;
json["TriggerValue"] = 5;
QJsonArray sensorsArray;
QJsonObject sensor1;
sensor1["Type"] = Sensor::TYPE_TEMPERATURE;
sensor1["Id"] = 10;
sensor1["Name"] = "loaded_temp";
sensorsArray.append(sensor1);
json["Sensors"] = sensorsArray;
ScriptActor actor;
actor.load(json, false);
QVERIFY(actor.getWatchParentItem() == true);
QVERIFY(actor.getScriptPath() == "/custom/script.py");
QVERIFY(actor.getScriptCode() == "return 99");
QVERIFY(actor.getTriggerValue() == 5);
QVERIFY(actor.isActive() == true);
QVERIFY(actor.getSensors().size() == 1);
QVERIFY(actor.getSensors()[0].type == Sensor::TYPE_TEMPERATURE);
QVERIFY(actor.getSensors()[0].id == 10);
QVERIFY(actor.getSensors()[0].name == "loaded_temp");
}
void testScriptActorLoadActor()
{
QJsonObject json;
json["Type"] = "Script";
json["WatchParentItem"] = false;
json["ScriptCode"] = "return 42";
json["Active"] = true;
json["Exausted"] = false;
json["TriggerValue"] = 1;
std::shared_ptr<Actor> actor = Actor::loadActor(json);
QVERIFY(actor != nullptr);
// Verify the actor was loaded with correct values
QVERIFY(actor->isActive());
QVERIFY(!actor->isExausted());
QVERIFY(actor->getTriggerValue() == 1);
}
void testScriptActorGetName()
{
ScriptActor actor;
// Without sensors, name should just be "Script"
QString name = actor.getName();
QVERIFY(name == "Script");
// With sensors
Sensor sensor1(Sensor::TYPE_TEMPERATURE, 1, 25.0, "temp_sensor");
actor.addSensor(sensor1);
name = actor.getName();
QVERIFY(name.contains("Script"));
QVERIFY(name.contains("temp_sensor"));
// With parent item watching
actor.setWatchParentItem(true);
name = actor.getName();
QVERIFY(name.contains("parent item"));
}
void testScriptActorTriggerSync()
{
ScriptActor actor;
actor.setTriggerValue(5);
// triggerSync should not crash
// It emits sigItemUpdate which will be handled by parent item
actor.triggerSync();
// Verify trigger value is unchanged
QVERIFY(actor.getTriggerValue() == 5);
}
void testScriptActorRunScript()
{
ScriptActor actor;
actor.setTriggerValue(0);
// Set a simple script that returns 42
actor.setScriptCode("return 42");
// Run the script
QVariant result = actor.runScriptNow();
// Verify the result is 42
QVERIFY(result.isValid());
QVERIFY(result.toInt() == 42);
// Note: performValueAction emits sigItemUpdate but doesn't update triggerValue
// triggerValue is set manually and used for manual activation
}
void testScriptActorRunScriptWithDicts()
{
ScriptActor actor;
actor.setTriggerValue(0);
// Set a script that uses the available dicts
actor.setScriptCode("return len(items) + len(sensors)");
// Run the script - should not crash
QVariant result = actor.runScriptNow();
// Result should be valid (number of items + sensors)
QVERIFY(result.isValid());
}
void testScriptActorRunScriptMultipleTimes()
{
ScriptActor actor;
actor.setTriggerValue(0);
// Set a script that increments
actor.setScriptCode("return 1");
// Run multiple times - should not crash
for(int i = 0; i < 3; i++)
{
QVariant result = actor.runScriptNow();
QVERIFY(result.isValid());
QVERIFY(result.toInt() == 1);
}
}
void cleanupTestCase()
{
// Cleanup after all tests