Преглед изворни кода

improvements all over. most notable: added proper testing

Malte Veerman пре 9 година
родитељ
комит
8e9343bc1a

+ 0 - 4
CMakeLists.txt

@@ -112,9 +112,5 @@ if(INSTALL_SHARED)
 endif(INSTALL_SHARED)
 
 
-#add tests
-#add_subdirectory(tests)
-
-
 #summary
 feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)

+ 1 - 1
fancontrol-gui/package/contents/ui/Application.qml

@@ -37,7 +37,7 @@ ApplicationWindow {
         Fancontrol.base.save();
         windowConfig.save(window);
     }
-    
+
     Component.onCompleted: {
         Fancontrol.base.load();
         windowConfig.restore(window);

+ 7 - 0
import/CMakeLists.txt

@@ -50,3 +50,10 @@ target_link_libraries(fancontrol_qml_plugin PRIVATE ${LIB_PRIVATE_LIBRARIES} PUB
 
 install(TARGETS fancontrol_qml_plugin DESTINATION "${KDE_INSTALL_QMLDIR}/Fancontrol/Qml/")
 install(FILES ${QML_FILES} DESTINATION "${KDE_INSTALL_QMLDIR}/Fancontrol/Qml/")
+
+
+#tests
+
+if(BUILD_TESTING)
+    add_subdirectory(tests)
+endif(BUILD_TESTING)

+ 39 - 25
import/src/fan.cpp

@@ -28,16 +28,20 @@
 
 #include <KConfigCore/KSharedConfig>
 #include <KConfigCore/KConfigGroup>
+#include <KI18n/KLocalizedString>
+
+
+#define TEST_HWMON_NAME "test"
 
 
 namespace Fancontrol
 {
 
-Fan::Fan(Hwmon *parent, uint index) :
-    Sensor(parent, index, QString(parent->name() + QStringLiteral("/fan") + QString::number(index))),
-    m_rpmStream(new QTextStream)
+Fan::Fan(uint index, Hwmon *parent) : Sensor(parent, index, parent ? parent->name() + "/fan" + QString::number(index) : QString()),
+    m_rpmStream(new QTextStream),
+    m_rpm(0)
 {
-    if (QDir(parent->path()).isReadable())
+    if (m_parent && QDir(parent->path()).isReadable())
     {
         const auto rpmFile = new QFile(parent->path() + "/fan" + QString::number(index) + "_input", this);
 
@@ -48,35 +52,36 @@ Fan::Fan(Hwmon *parent, uint index) :
         }
         else
         {
+            emit error(i18n("Can't open rpmFile: %1", rpmFile->fileName()));
             delete rpmFile;
-            emit error("Can't open rpmFile: " + parent->path() + "/fan" + QString::number(index) + "_input");
         }
     }
 }
 
 Fan::~Fan()
 {
-    delete m_rpmStream->device();
+    auto device = m_rpmStream->device();
     delete m_rpmStream;
+    delete device;
 }
 
 QString Fan::name() const
 {
     const auto names = KSharedConfig::openConfig(QStringLiteral("fancontrol-gui"))->group("names");
-    const auto localNames = names.group(m_parent->name());
+    const auto localNames = names.group(m_parent ? m_parent->name() : QStringLiteral(TEST_HWMON_NAME));
     const auto name = localNames.readEntry("fan" + QString::number(m_index), QString());
-    
+
     if (name.isEmpty())
         return "fan" + QString::number(m_index);
-    
+
     return name;
 }
 
 void Fan::setName(const QString &name)
 {
     const auto names = KSharedConfig::openConfig(QStringLiteral("fancontrol-gui"))->group("names");
-    auto localNames = names.group(m_parent->name());
-    
+    auto localNames = names.group(m_parent ? m_parent->name() : QStringLiteral(TEST_HWMON_NAME));
+
     if (name != localNames.readEntry("fan" + QString::number(m_index), QString())
         && !name.isEmpty())
     {
@@ -87,22 +92,26 @@ void Fan::setName(const QString &name)
 
 void Fan::reset()
 {
-    delete m_rpmStream->device();
-    delete m_rpmStream;
-
-    if (QDir(m_parent->path()).isReadable())
+    if (m_rpmStream->device() && m_parent)
     {
-        const auto rpmFile = new QFile(m_parent->path() + "/fan" + QString::number(m_index) + "_input", this);
+        auto device = m_rpmStream->device();
+        m_rpmStream->setDevice(Q_NULLPTR);
+        delete device;
 
-        if (rpmFile->open(QFile::ReadOnly))
+        if (QDir(m_parent->path()).isReadable())
         {
-            m_rpmStream = new QTextStream(rpmFile);
-            *m_rpmStream >> m_rpm;
-        }
-        else
-        {
-            delete rpmFile;
-            emit error("Can't open rpmFile: " + m_parent->path() + "/fan" + QString::number(m_index) + "_input");
+            const auto rpmFile = new QFile(m_parent->path() + "/fan" + QString::number(m_index) + "_input", this);
+
+            if (rpmFile->open(QFile::ReadOnly))
+            {
+                m_rpmStream->setDevice(rpmFile);
+                *m_rpmStream >> m_rpm;
+            }
+            else
+            {
+                emit error(i18n("Can't open rpmFile: %1", rpmFile->fileName()));
+                delete rpmFile;
+            }
         }
     }
 }
@@ -112,7 +121,7 @@ void Fan::update()
     m_rpmStream->seek(0);
     int rpm;
     *m_rpmStream >> rpm;
-    
+
     if (rpm != m_rpm)
     {
         m_rpm = rpm;
@@ -120,4 +129,9 @@ void Fan::update()
     }
 }
 
+bool Fan::isValid() const
+{
+    return m_rpmStream->device() || m_rpmStream->string();
+}
+
 }

+ 8 - 3
import/src/fan.h

@@ -37,13 +37,14 @@ class Fan : public Sensor
 
 public:
 
-    explicit Fan(Hwmon *parent, uint index);
+    explicit Fan(uint index, Hwmon *parent = Q_NULLPTR);
     virtual ~Fan();
 
     int rpm() const { return m_rpm; }
     QString name() const Q_DECL_OVERRIDE;
     void setName(const QString &name) Q_DECL_OVERRIDE;
     void reset() Q_DECL_OVERRIDE;
+    bool isValid() const Q_DECL_OVERRIDE;
 
     virtual int pwm() const { return 255; }
     virtual bool setPwm(int, bool) { return false; }
@@ -61,10 +62,14 @@ public slots:
 
 protected:
 
-    int m_rpm;
     QTextStream *m_rpmStream;
+
+
+private:
+
+    int m_rpm;
 };
 
 }
 
-#endif // FAN_H
+#endif // FAN_H

+ 2 - 0
import/src/guibase.cpp

@@ -57,6 +57,8 @@ GUIBase::GUIBase(QObject *parent) : QObject(parent),
     m_unit = (system == QLocale::ImperialUSSystem) ? QStringLiteral("°F") : QStringLiteral("°C");
     emit unitChanged(m_unit);
 
+    m_loader->parseHwmons();
+
     foreach (const auto &hwmon, m_loader->hwmons())
     {
         m_pwmFanModel->addPwmFans(hwmon->pwmFans());

+ 4 - 4
import/src/guibase.h

@@ -83,17 +83,17 @@ public:
     void setUnit(const QString &unit) { if (unit != m_unit) { m_unit = unit; emit unitChanged(m_unit); } }
     PwmFanModel *pwmFanModel() const { return m_pwmFanModel; }
     TempModel *tempModel() const { return m_tempModel; }
-    
+
     Q_INVOKABLE bool hasSystemdCommunicator() const;
 
 
 public slots:
-    
+
     void save(bool saveLoader = false, const QUrl &url = QUrl());
     void load();
     void handleError(const QString &error, bool critical = false);
-    
-    
+
+
 signals:
 
     void minTempChanged();

+ 58 - 45
import/src/hwmon.cpp

@@ -21,9 +21,13 @@
 #include "hwmon.h"
 
 #include "loader.h"
+#include "temp.h"
+#include "fan.h"
+#include "pwmfan.h"
 
 #include <QtCore/QDir>
 #include <QtCore/QTextStream>
+#include <KI18n/KLocalizedString>
 
 
 namespace Fancontrol
@@ -31,40 +35,44 @@ namespace Fancontrol
 
 Hwmon::Hwmon(const QString &path, Loader *parent) : QObject(parent),
     m_parent(parent),
-    m_path(path),
-    m_valid(true)
+    m_valid(true),
+    m_path(path)
 {
-    QDir dir(path);
-    if (!dir.isReadable())
+    if (!path.isEmpty())
     {
-        emit error(path + " is not readable!");
-        m_valid = false;
-    }
+        QDir dir(path);
+        if (!dir.isReadable())
+        {
+            emit error(i18n("%1 is not readable!", path));
+            m_valid = false;
+        }
 
-    auto success = false;
-    m_index = path.split('/').last().remove(QStringLiteral("hwmon")).toInt(&success);
+        auto success = false;
+        m_index = path.split('/').last().remove(QStringLiteral("hwmon")).toInt(&success);
 
-    if (!success)
-    {
-        emit error(path + "is invalid!");
-        m_valid = false;
-    }
+        if (!success)
+        {
+            emit error(i18n("%1 is invalid!", path));
+            m_valid = false;
+        }
 
-    const auto nameFile = new QFile(path + "/name");
+        const auto nameFile = new QFile(path + "/name");
 
-    if (nameFile->open(QFile::ReadOnly))
-        m_name = QTextStream(nameFile).readLine();
-    else
-        m_name = path.split('/').last();
+        if (nameFile->open(QFile::ReadOnly))
+            m_name = QTextStream(nameFile).readLine();
+        else
+            m_name = path.split('/').last();
 
-    delete nameFile;
+        delete nameFile;
+    }
 
-    connect(this, &Hwmon::configUpdateNeeded, parent, &Loader::updateConfig);
-    connect(this, &Hwmon::pwmFansChanged, parent, &Loader::emitAllPwmFansChanged);
-    connect(this, &Hwmon::tempsChanged, parent, &Loader::emitAllTempsChanged);
-    connect(this, &Hwmon::error, parent, &Loader::error);
+    if (parent)
+    {
+        connect(this, &Hwmon::configUpdateNeeded, parent, &Loader::updateConfig);
+        connect(this, &Hwmon::error, parent, &Loader::error);
+    }
 
-    if (m_valid)
+    if (m_valid && !m_path.isEmpty())
         initialize();
 }
 
@@ -83,7 +91,7 @@ void Hwmon::initialize()
 
         if (!success)
         {
-            emit error("Not a valid Sensor:" + entry);
+            emit error(i18n("Not a valid Sensor: %1", entry));
             continue;
         }
 
@@ -104,9 +112,12 @@ void Hwmon::initialize()
 
                 if (!newPwmFan)
                 {
-                    newPwmFan = new PwmFan(this, index);
+                    newPwmFan = new PwmFan(index, this);
                     connect(this, &Hwmon::sensorsUpdateNeeded, newPwmFan, &PwmFan::update);
-                    connect(newPwmFan, &PwmFan::testStatusChanged, m_parent, &Loader::handleTestStatusChanged);
+
+                    if (m_parent)
+                        connect(newPwmFan, &PwmFan::testStatusChanged, m_parent, &Loader::handleTestStatusChanged);
+
                     m_pwmFans << newPwmFan;
                     emit pwmFansChanged();
                 }
@@ -134,7 +145,7 @@ void Hwmon::initialize()
 
                 if (!newFan)
                 {
-                    newFan = new Fan(this, index);
+                    newFan = new Fan(index, this);
                     connect(this, &Hwmon::sensorsUpdateNeeded, newFan, &Fan::update);
                     m_fans << newFan;
                     emit fansChanged();
@@ -158,7 +169,7 @@ void Hwmon::initialize()
 
             if (!newTemp)
             {
-                newTemp = new Temp(this, index);
+                newTemp = new Temp(index, this);
                 connect(this, &Hwmon::sensorsUpdateNeeded, newTemp, &Temp::update);
                 m_temps << newTemp;
                 emit tempsChanged();
@@ -170,47 +181,43 @@ void Hwmon::initialize()
 QList<QObject *> Hwmon::fansAsObjects() const
 {
     QList<QObject *> list;
+
     foreach (const auto &fan, m_fans)
-    {
         list << qobject_cast<QObject *>(fan);
-    }
+
     return list;
 }
 
 QList<QObject *> Hwmon::pwmFansAsObjects() const
 {
     QList<QObject *> list;
+
     foreach (const auto &pwmFan, m_pwmFans)
-    {
         list << qobject_cast<QObject *>(pwmFan);
-    }
+
     return list;
 }
 
 QList<QObject *> Hwmon::tempsAsObjects() const
 {
     QList<QObject *> list;
+
     foreach (const auto &temp, m_temps)
-    {
         list << qobject_cast<QObject *>(temp);
-    }
+
     return list;
 }
 
 void Hwmon::testFans()
 {
-    foreach (const auto &fan, m_pwmFans)
-    {
-        fan->test();
-    }
+    foreach (const auto &pwmFan, m_pwmFans)
+        pwmFan->test();
 }
 
 void Hwmon::abortTestingFans()
 {
-    foreach (const auto &fan, m_pwmFans)
-    {
-        fan->abortTest();
-    }
+    foreach (const auto &pwmFan, m_pwmFans)
+        pwmFan->abortTest();
 }
 
 Fan* Hwmon::fan(int i) const
@@ -232,7 +239,7 @@ bool Hwmon::testing() const
 {
     auto testing = false;
 
-    foreach(const auto &fan, m_pwmFans)
+    foreach (const auto &fan, m_pwmFans)
     {
         if (fan->testing())
         {
@@ -244,4 +251,10 @@ bool Hwmon::testing() const
     return testing;
 }
 
+void Hwmon::reset() const
+{
+    foreach (const auto &pwmFan, m_pwmFans)
+        pwmFan->reset();
+}
+
 }

+ 11 - 15
import/src/hwmon.h

@@ -25,15 +25,14 @@
 #include <QtCore/QString>
 #include <QtCore/QList>
 
-#include "temp.h"
-#include "fan.h"
-#include "pwmfan.h"
-
 
 namespace Fancontrol
 {
 
 class Loader;
+class Fan;
+class Temp;
+class PwmFan;
 
 class Hwmon : public QObject
 {
@@ -67,12 +66,7 @@ public:
     Temp * temp(int i) const;
     bool isValid() const { return m_valid; }
     bool testing() const;
-
-
-public slots:
-
-    void updateConfig() { emit configUpdateNeeded(); }
-    void updateSensors() { emit sensorsUpdateNeeded(); }
+    void reset() const;
 
 
 signals:
@@ -85,19 +79,21 @@ signals:
     void error(QString, bool = false);
 
 
-private:
+protected:
 
-    Loader *m_parent;
     QString m_name;
-    const QString m_path;
-    bool m_valid;
     int m_index;
+    Loader *const m_parent;
+    bool m_valid;
     QList<Fan *> m_fans;
     QList<PwmFan *> m_pwmFans;
     QList<Temp *> m_temps;
+
+private:
+
+    const QString m_path;
 };
 
 }
 
-
 #endif // HWMON_H

+ 196 - 139
import/src/loader.cpp

@@ -22,6 +22,8 @@
 
 #include "guibase.h"
 #include "hwmon.h"
+#include "pwmfan.h"
+#include "fan.h"
 #include "fancontrolaction.h"
 
 #include <QtCore/QFile>
@@ -54,12 +56,10 @@ Loader::Loader(GUIBase *parent) : QObject(parent),
     if (parent)
         connect(this, &Loader::error, parent, &GUIBase::handleError);
 
-    parseHwmons();
-
     m_timer->setSingleShot(false);
     m_timer->start(1);
 
-    connect(m_timer, &QTimer::timeout, this, &Loader::updateSensors);
+    connect(m_timer, &QTimer::timeout, this, &Loader::sensorsUpdateNeeded);
 }
 
 void Loader::parseHwmons()
@@ -71,12 +71,12 @@ void Loader::parseHwmons()
 
     else if (hwmonDir.exists())
     {
-        emit error(i18n("%1 is not readable!", QStringLiteral(HWMON_PATH)), true);
+        emit error(i18n("File is not readable: \"%1\"", QStringLiteral(HWMON_PATH)), true);
         return;
     }
     else
     {
-        emit error(i18n("%1 does not exist!", QStringLiteral(HWMON_PATH)), true);
+        emit error(i18n("File does not exist: \"%1\"", QStringLiteral(HWMON_PATH)), true);
         return;
     }
 
@@ -114,7 +114,7 @@ void Loader::parseHwmons()
             auto newHwmon = new Hwmon(hwmonPath, this);
             if (newHwmon->isValid())
             {
-                connect(this, &Loader::sensorsUpdateNeeded, newHwmon, &Hwmon::updateSensors);
+                connect(this, &Loader::sensorsUpdateNeeded, newHwmon, &Hwmon::sensorsUpdateNeeded);
                 m_hwmons << newHwmon;
                 emit hwmonsChanged();
             }
@@ -124,24 +124,34 @@ void Loader::parseHwmons()
     }
 }
 
-PwmFan * Loader::getPwmFan(const QPair<int, int> &indexPair) const
+PwmFan * Loader::pwmFan(int hwmonIndex, int pwmFanIndex) const
+{
+    const auto hwmon = m_hwmons.value(hwmonIndex, Q_NULLPTR);
+
+    if (!hwmon)
+        return Q_NULLPTR;
+
+    return hwmon->pwmFan(pwmFanIndex);
+}
+
+Temp * Loader::temp(int hwmonIndex, int tempIndex) const
 {
-    const auto hwmon = m_hwmons.value(indexPair.first, Q_NULLPTR);
+    const auto hwmon = m_hwmons.value(hwmonIndex, Q_NULLPTR);
 
     if (!hwmon)
         return Q_NULLPTR;
 
-    return hwmon->pwmFan(indexPair.second);
+    return hwmon->temp(tempIndex);
 }
 
-Temp * Loader::getTemp(const QPair<int, int> &indexPair) const
+Fan * Loader::fan(int hwmonIndex, int fanIndex) const
 {
-    const auto hwmon = m_hwmons.value(indexPair.first, Q_NULLPTR);
+    const auto hwmon = m_hwmons.value(hwmonIndex, Q_NULLPTR);
 
     if (!hwmon)
         return Q_NULLPTR;
 
-    return hwmon->temp(indexPair.second);
+    return hwmon->fan(fanIndex);
 }
 
 QPair<int, int> Loader::getEntryNumbers(const QString &entry)
@@ -152,7 +162,7 @@ QPair<int, int> Loader::getEntryNumbers(const QString &entry)
     auto list = entry.split('/', QString::SkipEmptyParts);
     if (list.size() != 2)
     {
-        emit error(i18n("Invalid entry to parse: %1", entry));
+        emit error(i18n("Invalid entry to parse: \"%1\"", entry));
         return QPair<int, int>(-1, -1);
     }
     auto &hwmon = list[0];
@@ -160,12 +170,12 @@ QPair<int, int> Loader::getEntryNumbers(const QString &entry)
 
     if (!hwmon.startsWith(QStringLiteral("hwmon")))
     {
-        emit error(i18n("Invalid entry to parse: %1", entry));
+        emit error(i18n("Invalid entry to parse: \"%1\"", entry));
         return QPair<int, int>(-1, -1);
     }
     if (!sensor.contains(QRegExp("^(pwm|fan|temp)\\d+")))
     {
-        emit error(i18n("Invalid entry to parse: %1", entry));
+        emit error(i18n("Invalid entry to parse: \"%1\"", entry));
         return QPair<int, int>(-1, -1);
     }
 
@@ -178,118 +188,21 @@ QPair<int, int> Loader::getEntryNumbers(const QString &entry)
     const auto hwmonResult = hwmon.toInt(&success);
     if (!success)
     {
-        emit error(i18n("Invalid entry to parse: %1", entry));
+        emit error(i18n("Invalid entry to parse: \"%1\"", entry));
         return QPair<int, int>(-1, -1);
     }
     const auto sensorResult = sensor.toInt(&success);
     if (!success)
     {
-        emit error(i18n("Invalid entry to parse: %1", entry));
+        emit error(i18n("Invalid entry to parse: \"%1\"", entry));
         return QPair<int, int>(-1, -1);
     }
 
     return QPair<int, int>(hwmonResult, sensorResult - 1);
 }
 
-void Loader::parseConfigLine(const QString &line, void (PwmFan::*memberSetFunction)(int))
-{
-    if (!memberSetFunction)
-        return;
-
-    const auto entries = line.split(' ');
-
-    foreach (const auto &entry, entries)
-    {
-        const auto fanValuePair = entry.split('=');
-        if (fanValuePair.size() == 2)
-        {
-            const auto fanString = fanValuePair.at(0);
-            const auto valueString = fanValuePair.at(1);
-            auto success = false;
-            const auto value = valueString.toInt(&success);
-
-            if (success)
-            {
-                auto fan = getPwmFan(getEntryNumbers(fanString));
-                if (fan)
-                    (fan->*memberSetFunction)(value);
-            }
-            else
-                emit error(valueString + " is not an int");
-        }
-        else
-            emit error(i18n("Invalid entry to parse: %1", entry));
-    }
-}
-
-bool Loader::load(const QUrl &url)
+bool Loader::parseConfig(QString config)
 {
-    QString fileName;
-    if (url.isEmpty())
-    {
-//        qDebug() << "Given empty url. Fallback to" << m_configUrl;
-        fileName = m_configUrl.toLocalFile();
-    }
-    else if (url.isValid())
-    {
-        if (url.isLocalFile())
-            fileName = url.toLocalFile();
-
-        else
-        {
-            emit error(i18n("%1 is not a local file!", url.toDisplayString()));
-            return false;
-        }
-    }
-    else
-    {
-        emit error(i18n("%1 is not a valid url!", url.toDisplayString()));
-        return false;
-    }
-
-    QTextStream stream;
-    QFile file(fileName);
-    QString fileContent;
-
-    if (file.open(QFile::ReadOnly | QFile::Text))
-    {
-        stream.setDevice(&file);
-        fileContent = stream.readAll();
-    }
-    else if (file.exists())
-    {
-        auto action = newFancontrolAction();
-
-        if (action.isValid())
-        {
-            auto map = QVariantMap();
-            map[QStringLiteral("action")] = QVariant("read");
-            map[QStringLiteral("filename")] = fileName;
-            action.setArguments(map);
-            auto reply = action.execute();
-            if (!reply->exec())
-            {
-                if (reply->error() == 4)
-                {
-//                    qDebug() << "Aborted by user";
-                    return false;
-                }
-
-                emit error(reply->errorString() + reply->errorText(), true);
-                return false;
-            }
-            else
-                fileContent = reply->data().value(QStringLiteral("content")).toString();
-        }
-        else
-            emit error(i18n("Action not supported! Try running the application as root."), true);
-    }
-    else
-    {
-        emit error(i18n("File does not exist: %1" ,fileName));
-        return false;
-    }
-
     //Disconnect hwmons for performance reasons
     //They get reconnected later
     foreach (const auto &hwmon, m_hwmons)
@@ -301,8 +214,11 @@ bool Loader::load(const QUrl &url)
         }
     }
 
-    stream.setString(&fileContent);
-    auto lines = QStringList();
+    reset();
+
+    QTextStream stream;
+    stream.setString(&config, QIODevice::ReadOnly);
+    QStringList lines;
     do
     {
         auto line(stream.readLine());
@@ -313,7 +229,7 @@ bool Loader::load(const QUrl &url)
         const auto offset = line.indexOf('#');
 
         if (offset != -1)
-            line.truncate(offset-1);
+            line.truncate(offset);
 
         line = line.simplified();
         lines << line;
@@ -325,6 +241,7 @@ bool Loader::load(const QUrl &url)
         if (line.startsWith(QStringLiteral("INTERVAL=")))
         {
             line.remove(QStringLiteral("INTERVAL="));
+            line = line.simplified();
             auto success = false;
             const auto interval = line.toInt(&success);
 
@@ -336,23 +253,24 @@ bool Loader::load(const QUrl &url)
                 foreach (const auto &hwmon, m_hwmons)
                     connect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::updateConfig);
 
-                emit error(i18n("Unable to parse interval line: %1", line), true);
+                emit error(i18n("Unable to parse interval line: \"%1\"", line), true);
                 return false;
             }
         }
         else if (line.startsWith(QStringLiteral("FCTEMPS=")))
         {
             line.remove(QStringLiteral("FCTEMPS="));
+            line = line.simplified();
             const auto fctemps = line.split(' ');
             foreach (const auto &fctemp, fctemps)
             {
                 const auto nameValuePair = fctemp.split('=');
                 if (nameValuePair.size() == 2)
                 {
-                    const auto pwm = nameValuePair.at(0);
-                    const auto temp = nameValuePair.at(1);
-                    const auto pwmPointer = getPwmFan(getEntryNumbers(pwm));
-                    const auto tempPointer = getTemp(getEntryNumbers(temp));
+                    const auto pwmFanString = nameValuePair.at(0);
+                    const auto tempString = nameValuePair.at(1);
+                    const auto pwmPointer = pwmFan(getEntryNumbers(pwmFanString));
+                    const auto tempPointer = temp(getEntryNumbers(tempString));
 
                     if (pwmPointer && tempPointer)
                     {
@@ -360,18 +278,28 @@ bool Loader::load(const QUrl &url)
                         pwmPointer->setHasTemp(true);
                         pwmPointer->setMinPwm(0);
                     }
+                    else
+                    {
+                        if (!pwmPointer)
+                            emit error(i18n("Invalid fan entry: \"%1\"", pwmFanString), true);
+
+                        if (!tempPointer)
+                            emit error(i18n("Invalid temp entry: \"%1\"", tempString), true);
+                    }
                 }
                 else
-                    emit error(i18n("Invalid entry: %1", fctemp));
+                    emit error(i18n("Invalid entry: \"%1\"", fctemp), true);
             }
         }
         else if (line.startsWith(QStringLiteral("DEVNAME=")))
         {
             line.remove(QStringLiteral("DEVNAME="));
+            line = line.simplified();
             const auto devnames = line.split(' ');
             foreach (const auto &devname, devnames)
             {
                 const auto indexNamePair = devname.split('=');
+
                 if (indexNamePair.size() == 2)
                 {
                     auto index = indexNamePair.at(0);
@@ -386,51 +314,63 @@ bool Loader::load(const QUrl &url)
                         foreach (const auto &hwmon, m_hwmons)
                             connect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::updateConfig);
 
-                        emit error(i18n("Can not parse %1", devname), true);
+                        emit error(i18n("Invalid DEVNAME: \"%1\"!", devname), true);
                         return false;
                     }
 
-                    if (!hwmonPointer || hwmonPointer->name().split('.').first() != name)
+                    if (!hwmonPointer)
                     {
                         //Connect hwmons again
                         foreach (const auto &hwmon, m_hwmons)
                             connect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::updateConfig);
 
-                        emit error(i18n("Invalid config file!"), true);
+                        emit error(i18n("Invalid DEVNAME: \"%1\"! No hwmon with index %2", devname, index), true);
+                        return false;
+                    }
+
+                    if (hwmonPointer->name().split('.').first() != name)
+                    {
+                        //Connect hwmons again
+                        foreach (const auto &hwmon, m_hwmons)
+                            connect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::updateConfig);
+
+                        emit error(i18n("Wrong name for hwmon %1! Should be \"%2\"", index, hwmonPointer->name().split('.').first()), true);
                         return false;
                     }
                 }
+                else
+                    emit error(i18n("Invalid DEVNAME: \"%1\"!", devname), true);
             }
         }
         else if (line.startsWith(QStringLiteral("MINTEMP=")))
         {
             line.remove(QStringLiteral("MINTEMP="));
-            parseConfigLine(line, &PwmFan::setMinTemp);
+            parseConfigLine(line.simplified(), &PwmFan::setMinTemp);
         }
         else if (line.startsWith(QStringLiteral("MAXTEMP=")))
         {
             line.remove(QStringLiteral("MAXTEMP="));
-            parseConfigLine(line, &PwmFan::setMaxTemp);
+            parseConfigLine(line.simplified(), &PwmFan::setMaxTemp);
         }
         else if (line.startsWith(QStringLiteral("MINSTART=")))
         {
             line.remove(QStringLiteral("MINSTART="));
-            parseConfigLine(line, &PwmFan::setMinStart);
+            parseConfigLine(line.simplified(), &PwmFan::setMinStart);
         }
         else if (line.startsWith(QStringLiteral("MINSTOP=")))
         {
             line.remove(QStringLiteral("MINSTOP="));
-            parseConfigLine(line, &PwmFan::setMinStop);
+            parseConfigLine(line.simplified(), &PwmFan::setMinStop);
         }
         else if (line.startsWith(QStringLiteral("MINPWM=")))
         {
             line.remove(QStringLiteral("MINPWM="));
-            parseConfigLine(line, &PwmFan::setMinPwm);
+            parseConfigLine(line.simplified(), &PwmFan::setMinPwm);
         }
         else if (line.startsWith(QStringLiteral("MAXPWM=")))
         {
             line.remove(QStringLiteral("MAXPWM="));
-            parseConfigLine(line, &PwmFan::setMaxPwm);
+            parseConfigLine(line.simplified(), &PwmFan::setMaxPwm);
         }
         else if (!line.startsWith(QStringLiteral("DEVPATH=")) &&
             !line.startsWith(QStringLiteral("FCFANS=")))
@@ -439,7 +379,7 @@ bool Loader::load(const QUrl &url)
             foreach (const auto &hwmon, m_hwmons)
                 connect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::updateConfig);
 
-            emit error(i18n("Unrecognized line in config: %1", line), true);
+            emit error(i18n("Unrecognized line in config: \"%1\"", line), true);
             return false;
         }
     }
@@ -450,13 +390,119 @@ bool Loader::load(const QUrl &url)
     foreach (const auto &hwmon, m_hwmons)
         connect(hwmon, &Hwmon::configUpdateNeeded, this, &Loader::updateConfig);
 
-    if (!url.isEmpty())
+    return true;
+}
+
+void Loader::parseConfigLine(const QString &line, void (PwmFan::*memberSetFunction)(int))
+{
+    if (!memberSetFunction)
+        return;
+
+    const auto entries = line.split(' ');
+
+    foreach (const auto &entry, entries)
+    {
+        const auto fanValuePair = entry.split('=');
+        if (fanValuePair.size() == 2)
+        {
+            const auto pwmFanString = fanValuePair.at(0);
+            const auto valueString = fanValuePair.at(1);
+            auto success = false;
+            const auto value = valueString.toInt(&success);
+
+            if (success)
+            {
+                auto pwmFanPointer = pwmFan(getEntryNumbers(pwmFanString));
+                if (pwmFanPointer)
+                    (pwmFanPointer->*memberSetFunction)(value);
+                else
+                    emit error(i18n("Invalid fan entry: \"%1\"", pwmFanString), true);
+            }
+            else
+                emit error(i18n("%1 is not an integer!", valueString));
+        }
+        else
+            emit error(i18n("Invalid entry to parse: \"%1\"", entry));
+    }
+}
+
+bool Loader::load(const QUrl &url)
+{
+    QString fileName;
+    if (url.isEmpty())
+    {
+//        qDebug() << "Given empty url. Fallback to" << m_configUrl;
+        fileName = m_configUrl.toLocalFile();
+    }
+    else if (url.isValid())
+    {
+        if (url.isLocalFile())
+            fileName = url.toLocalFile();
+
+        else
+        {
+            emit error(i18n("%1 is not a local file!", url.toDisplayString()));
+            return false;
+        }
+    }
+    else
+    {
+        emit error(i18n("%1 is not a valid url!", url.toDisplayString()));
+        return false;
+    }
+
+    QTextStream stream;
+    QFile file(fileName);
+    QString fileContent;
+
+    if (file.open(QFile::ReadOnly | QFile::Text))
+    {
+        stream.setDevice(&file);
+        fileContent = stream.readAll();
+    }
+    else if (file.exists())
+    {
+        auto action = newFancontrolAction();
+
+        if (action.isValid())
+        {
+            auto map = QVariantMap();
+            map[QStringLiteral("action")] = QVariant("read");
+            map[QStringLiteral("filename")] = fileName;
+            action.setArguments(map);
+            auto reply = action.execute();
+            if (!reply->exec())
+            {
+                if (reply->error() == 4)
+                {
+//                    qDebug() << "Aborted by user";
+                    return false;
+                }
+
+                emit error(reply->errorString() + reply->errorText(), true);
+                return false;
+            }
+            else
+                fileContent = reply->data().value(QStringLiteral("content")).toString();
+        }
+        else
+            emit error(i18n("Action not supported! Try running the application as root."), true);
+    }
+    else
+    {
+        emit error(i18n("File does not exist: %1" ,fileName));
+        return false;
+    }
+
+    auto success = parseConfig(fileContent);
+
+    if (success && !url.isEmpty())
     {
         m_configUrl = url;
         emit configUrlChanged();
     }
 
-    return true;
+    return success;
 }
 
 bool Loader::save(const QUrl &url)
@@ -534,12 +580,11 @@ QString Loader::createConfig() const
 
     foreach (const auto &hwmon, m_hwmons)
     {
-        if (hwmon->pwmFans().size() > 0)
+        if (hwmon->pwmFans().size() > 0 && !usedHwmons.contains(hwmon))
             usedHwmons << hwmon;
 
-        foreach (const auto &fan, hwmon->pwmFans())
+        foreach (const auto &pwmFan, hwmon->pwmFans())
         {
-            auto pwmFan = qobject_cast<PwmFan *>(fan);
             if (pwmFan->hasTemp() && pwmFan->temp() && !pwmFan->testing())
             {
                 usedFans << pwmFan;
@@ -665,6 +710,12 @@ QString Loader::createConfig() const
 
 void Loader::setInterval(int interval, bool writeNewConfig)
 {
+    if (interval < 1)
+    {
+        emit error(i18n("Interval must be greater or equal to one!"), true);
+        return;
+    }
+
     if (interval != m_interval)
     {
         m_interval = interval;
@@ -800,4 +851,10 @@ void Loader::setRestartServiceAfterTesting(bool restart)
     emit restartServiceAfterTestingChanged();
 }
 
+void Loader::reset() const
+{
+    foreach (const auto &hwmon, m_hwmons)
+        hwmon->reset();
+}
+
 }

+ 15 - 9
import/src/loader.h

@@ -38,6 +38,7 @@ namespace Fancontrol
 class Hwmon;
 class PwmFan;
 class Temp;
+class Fan;
 class GUIBase;
 
 class Loader : public QObject
@@ -70,30 +71,37 @@ public:
     QList<QObject *> hwmonsAsObjects() const;
     int interval() const { return m_interval; }
     void setInterval(int interval, bool writeNewConfig = true);
+    PwmFan *pwmFan(const QPair<int, int> &indexPair) const { return pwmFan(indexPair.first, indexPair.second); }
+    Temp *temp(const QPair<int, int> &indexPair) const { return temp(indexPair.first, indexPair.second); }
+    Fan *fan(const QPair<int, int> &indexPair) const { return fan(indexPair.first, indexPair.second); }
+    PwmFan *pwmFan(int hwmonIndex, int pwmFanIndex) const;
+    Temp *temp(int hwmonIndex, int tempIndex) const;
+    Fan *fan(int hwmonIndex, int fanIndex) const;
+    void reset() const;
 
 
 public slots:
 
-    void updateSensors() { emit sensorsUpdateNeeded(); }
     void updateConfig();
-    void emitAllPwmFansChanged() { emit allPwmFansChanged(); }
-    void emitAllTempsChanged() { emit allTempsChanged(); }
     void handleDetectSensorsResult(KJob *job);
     void handleDetectSensorsResult(int exitCode);
     void handleTestStatusChanged();
 
 
-private:
+protected:
 
+    bool parseConfig(QString config);
     void parseConfigLine(const QString &line, void (PwmFan::*memberSetFunction)(int value));
     QPair<int, int> getEntryNumbers(const QString &entry);
     QString createConfig() const;
-    PwmFan *getPwmFan(const QPair<int, int> &indexPair) const;
-    Temp *getTemp(const QPair<int, int> &indexPair) const;
+
+    QList<Hwmon *> m_hwmons;
+
+
+private:
 
     bool m_reactivateAfterTesting;
     int m_interval;
-    QList<Hwmon *> m_hwmons;
     QUrl m_configUrl;
     QString m_configFile;
     QTimer *m_timer;
@@ -108,8 +116,6 @@ signals:
     void intervalChanged();
     void error(QString, bool = false);
     void sensorsUpdateNeeded();
-    void allPwmFansChanged();
-    void allTempsChanged();
     void invalidConfigUrl();
     void sensorsDetectedChanged();
     void restartServiceAfterTestingChanged();

+ 178 - 91
import/src/pwmfan.cpp

@@ -37,15 +37,18 @@
 #include <KI18n/KLocalizedString>
 
 
+#define TEST_HWMON_NAME "test"
 #define MAX_ERRORS_FOR_RPM_ZERO 10
 
 
 namespace Fancontrol
 {
 
-PwmFan::PwmFan(Hwmon *parent, uint index) : Fan(parent, index),
+PwmFan::PwmFan(uint index, Hwmon *parent) : Fan(index, parent),
     m_pwmStream(new QTextStream),
     m_modeStream(new QTextStream),
+    m_pwm(0),
+    m_pwmMode(0),
     m_temp(Q_NULLPTR),
     m_hasTemp(false),
     m_minTemp(0),
@@ -57,60 +60,67 @@ PwmFan::PwmFan(Hwmon *parent, uint index) : Fan(parent, index),
     m_zeroRpm(0),
     m_testStatus(NotStarted)
 {
-    connect(this, &PwmFan::tempChanged, parent, &Hwmon::updateConfig);
-    connect(this, &PwmFan::hasTempChanged, parent, &Hwmon::updateConfig);
-    connect(this, &PwmFan::minTempChanged, parent, &Hwmon::updateConfig);
-    connect(this, &PwmFan::maxTempChanged, parent, &Hwmon::updateConfig);
-    connect(this, &PwmFan::minPwmChanged, parent, &Hwmon::updateConfig);
-    connect(this, &PwmFan::maxPwmChanged, parent, &Hwmon::updateConfig);
-    connect(this, &PwmFan::minStartChanged, parent, &Hwmon::updateConfig);
-    connect(this, &PwmFan::minStopChanged, parent, &Hwmon::updateConfig);
-    connect(this, &PwmFan::testStatusChanged, parent, &Hwmon::updateConfig);
-
-    if (QDir(parent->path()).isReadable())
+    if (parent)
     {
-        const auto pwmFile = new QFile(parent->path() + "/pwm" + QString::number(index), this);
-
-        if (pwmFile->open(QFile::ReadWrite))
-        {
-            m_pwmStream->setDevice(pwmFile);
-            *m_pwmStream >> m_pwm;
-        }
-        else if (pwmFile->open(QFile::ReadOnly))
+        connect(this, &PwmFan::tempChanged, parent, &Hwmon::configUpdateNeeded);
+        connect(this, &PwmFan::hasTempChanged, parent, &Hwmon::configUpdateNeeded);
+        connect(this, &PwmFan::minTempChanged, parent, &Hwmon::configUpdateNeeded);
+        connect(this, &PwmFan::maxTempChanged, parent, &Hwmon::configUpdateNeeded);
+        connect(this, &PwmFan::minPwmChanged, parent, &Hwmon::configUpdateNeeded);
+        connect(this, &PwmFan::maxPwmChanged, parent, &Hwmon::configUpdateNeeded);
+        connect(this, &PwmFan::minStartChanged, parent, &Hwmon::configUpdateNeeded);
+        connect(this, &PwmFan::minStopChanged, parent, &Hwmon::configUpdateNeeded);
+        connect(this, &PwmFan::testStatusChanged, parent, &Hwmon::configUpdateNeeded);
+
+        if (QDir(parent->path()).isReadable())
         {
-            m_pwmStream->setDevice(pwmFile);
-            *m_pwmStream >> m_pwm;
-        }
-        else
-        {
-            emit error("Can't open pwmFile: " + pwmFile->fileName());
-            delete pwmFile;
-        }
+            const auto pwmFile = new QFile(parent->path() + "/pwm" + QString::number(index), this);
 
-        const auto pwmModeFile = new QFile(parent->path() + "/pwm" + QString::number(index) + "_mode", this);
+            if (pwmFile->open(QFile::ReadWrite))
+            {
+                m_pwmStream->setDevice(pwmFile);
+                *m_pwmStream >> m_pwm;
+            }
+            else if (pwmFile->open(QFile::ReadOnly))
+            {
+                m_pwmStream->setDevice(pwmFile);
+                *m_pwmStream >> m_pwm;
+            }
+            else
+            {
+                emit error(i18n("Can't open pwmFile: %1", pwmFile->fileName()));
+                delete pwmFile;
+            }
 
-        if (pwmModeFile->open(QFile::ReadWrite))
-        {
-            m_modeStream->setDevice(pwmModeFile);
-            *m_modeStream >> m_pwmMode;
-        }
-        else if (pwmModeFile->open(QFile::ReadOnly))
-        {
-            m_modeStream->setDevice(pwmModeFile);
-            *m_modeStream >> m_pwmMode;
-        }
-        else
-        {
-            emit error("Can't open pwmModeFile: " + pwmModeFile->fileName());
-            delete pwmModeFile;
+            const auto pwmModeFile = new QFile(parent->path() + "/pwm" + QString::number(index) + "_mode", this);
+
+            if (pwmModeFile->open(QFile::ReadWrite))
+            {
+                m_modeStream->setDevice(pwmModeFile);
+                *m_modeStream >> m_pwmMode;
+            }
+            else if (pwmModeFile->open(QFile::ReadOnly))
+            {
+                m_modeStream->setDevice(pwmModeFile);
+                *m_modeStream >> m_pwmMode;
+            }
+            else
+            {
+                emit error(i18n("Can't open pwmModeFile: %1", pwmModeFile->fileName()));
+                delete pwmModeFile;
+            }
         }
     }
 }
 
 PwmFan::~PwmFan()
 {
+    auto device = m_pwmStream->device();
     delete m_pwmStream;
+    delete device;
+    device = m_modeStream->device();
     delete m_modeStream;
+    delete device;
 }
 
 void PwmFan::update()
@@ -130,52 +140,79 @@ void PwmFan::reset()
 
     setHasTemp(false);
     setTemp(Q_NULLPTR);
+    setPwm(0);
+    setPwmMode(0, true);
+    setMinTemp(0);
+    setMaxTemp(100);
+    setMinPwm(255);
+    setMaxPwm(255);
+    setMinStart(255);
+    setMinStop(255);
+    m_zeroRpm = 0;
+    m_testStatus = NotStarted;
+    emit testStatusChanged();
 
-    delete m_pwmStream->device();
-    delete m_pwmStream;
+    if (m_pwmStream->device() && m_modeStream->device() && m_parent)
+    {
+        auto device = m_pwmStream->device();
+        m_pwmStream->setDevice(Q_NULLPTR);
+        delete device;
 
-    delete m_modeStream->device();
-    delete m_modeStream;
+        device = m_modeStream->device();
+        m_modeStream->setDevice(Q_NULLPTR);
+        delete device;
 
-    const auto pwmFile = new QFile(m_parent->path() + "/pwm" + QString::number(m_index), this);
+        const auto pwmFile = new QFile(m_parent->path() + "/pwm" + QString::number(m_index), this);
 
-    if (pwmFile->open(QFile::ReadWrite))
-    {
-        m_pwmStream = new QTextStream(pwmFile);
-        *m_pwmStream >> m_pwm;
-    }
-    else if (pwmFile->open(QFile::ReadOnly))
-    {
-        m_pwmStream = new QTextStream(pwmFile);
-        *m_pwmStream >> m_pwm;
-    }
-    else
-    {
-        emit error("Can't open pwmFile: " + pwmFile->fileName());
-        delete pwmFile;
-    }
+        if (pwmFile->open(QFile::ReadWrite))
+        {
+            m_pwmStream->setDevice(pwmFile);
+            *m_pwmStream >> m_pwm;
+        }
+        else if (pwmFile->open(QFile::ReadOnly))
+        {
+            m_pwmStream->setDevice(pwmFile);
+            *m_pwmStream >> m_pwm;
+        }
+        else
+        {
+            emit error(i18n("Can't open pwmFile: %1", pwmFile->fileName()));
+            delete pwmFile;
+        }
 
-    const auto pwmModeFile = new QFile(m_parent->path() + "/pwm" + QString::number(m_index) + "_mode", this);
+        const auto pwmModeFile = new QFile(m_parent->path() + "/pwm" + QString::number(m_index) + "_mode", this);
 
-    if (pwmModeFile->open(QFile::ReadWrite))
-    {
-        m_modeStream = new QTextStream(pwmModeFile);
-        *m_modeStream >> m_pwmMode;
-    }
-    else if (pwmModeFile->open(QFile::ReadOnly))
-    {
-        m_modeStream = new QTextStream(pwmModeFile);
-        *m_modeStream >> m_pwmMode;
-    }
-    else
-    {
-        emit error("Can't open pwmModeFile: " + pwmModeFile->fileName());
-        delete pwmModeFile;
+        if (pwmModeFile->open(QFile::ReadWrite))
+        {
+            m_modeStream->setDevice(pwmModeFile);
+            *m_modeStream >> m_pwmMode;
+        }
+        else if (pwmModeFile->open(QFile::ReadOnly))
+        {
+            m_modeStream->setDevice(pwmModeFile);
+            *m_modeStream >> m_pwmMode;
+        }
+        else
+        {
+            emit error(i18n("Can't open pwmModeFile: %1", pwmModeFile->fileName()));
+            delete pwmModeFile;
+        }
     }
 }
 
+bool PwmFan::isValid() const
+{
+    return Fan::isValid() && (m_pwmStream->device() || m_pwmStream->string()) && (m_modeStream->device() || m_modeStream->string());
+}
+
 bool PwmFan::setPwm(int pwm, bool write)
 {
+    if (pwm < 0 || pwm > 255)
+    {
+        emit error(i18n("Pwm cannot exceed 0-255!"), true);
+        return false;
+    }
+
     if (m_pwm != pwm)
     {
         m_pwm = pwm;
@@ -185,7 +222,7 @@ bool PwmFan::setPwm(int pwm, bool write)
         {
             setPwmMode(1);
 
-            if (m_pwmStream->device()->isWritable())
+            if (m_pwmStream->string() || (m_pwmStream->device() && m_pwmStream->device()->isWritable()))
                 *m_pwmStream << pwm;
             else
             {
@@ -209,7 +246,7 @@ bool PwmFan::setPwm(int pwm, bool write)
                             QTimer::singleShot(50, this, [this] (){ setPwmMode(m_pwmMode); });
                         }
 
-                        emit error(i18n("Could not set pwm: ") + job->errorText());
+                        emit error(i18n("Could not set pwm: %1", job->errorText()));
                     }
                     update();
                 }
@@ -223,6 +260,12 @@ bool PwmFan::setPwm(int pwm, bool write)
 
 bool PwmFan::setPwmMode(int pwmMode, bool write)
 {
+    if (pwmMode < 0 || pwmMode > 2)
+    {
+        emit error(i18n("PwmMode cannot exceed 0-2!"), true);
+        return false;
+    }
+
     if (m_pwmMode != pwmMode)
     {
         m_pwmMode = pwmMode;
@@ -230,7 +273,7 @@ bool PwmFan::setPwmMode(int pwmMode, bool write)
 
         if (write)
         {
-            if (m_modeStream->device()->isWritable())
+            if (m_modeStream->string() || (m_modeStream->device() && m_modeStream->device()->isWritable()))
                 *m_modeStream << pwmMode;
 
             else
@@ -255,7 +298,7 @@ bool PwmFan::setPwmMode(int pwmMode, bool write)
                             QTimer::singleShot(50, this, [this] (){ setPwmMode(m_pwmMode); });
                         }
 
-                        emit error(i18n("Could not set pwm mode: ") + job->errorText());
+                        emit error(i18n("Could not set pwm mode: %1", job->errorText()));
                     }
                     update();
                 }
@@ -267,9 +310,40 @@ bool PwmFan::setPwmMode(int pwmMode, bool write)
     return true;
 }
 
+void PwmFan::setMinPwm(int minPwm)
+{
+    if (minPwm < 0 || minPwm > 255)
+    {
+        emit error(i18n("MinPwm cannot exceed 0-255!"), true);
+        return;
+    }
+
+    if (minPwm != m_minPwm)
+    {
+        m_minPwm = minPwm;
+        emit minPwmChanged();
+    }
+}
+
+void PwmFan::setMaxPwm(int maxPwm)
+{
+    if (maxPwm < 0 || maxPwm > 255)
+    {
+        emit error(i18n("MaxPwm cannot exceed 0-255!"), true);
+        return;
+    }
+
+    if (maxPwm != m_maxPwm)
+    {
+        m_maxPwm = maxPwm;
+        emit maxPwmChanged();
+    }
+}
+
 void PwmFan::test()
 {
-    if (!m_modeStream->device()->isWritable() || !m_pwmStream->device()->isWritable())
+    if ((!m_modeStream->device()->isWritable() && !m_modeStream->string()) ||
+        (!m_pwmStream->device()->isWritable() && !m_pwmStream->string()))
     {
         auto action = newFancontrolAction();
 
@@ -279,7 +353,7 @@ void PwmFan::test()
 
             if (!job->exec())
             {
-                emit error(i18n("Authorization error: ") + job->errorText());
+                emit error(i18n("Authorization error: %1", job->errorText()));
                 m_testStatus = Error;
                 emit testStatusChanged();
                 return;
@@ -316,7 +390,8 @@ void PwmFan::abortTest()
 
 void PwmFan::continueTest()
 {
-    if (!m_modeStream->device()->isWritable() || !m_pwmStream->device()->isWritable())
+    if ((!m_modeStream->device()->isWritable() && !m_modeStream->string()) ||
+        (!m_pwmStream->device()->isWritable() && !m_pwmStream->string()))
     {
         auto action = newFancontrolAction();
 
@@ -333,7 +408,7 @@ void PwmFan::continueTest()
     switch (m_testStatus)
     {
     case FindingStop1:
-        if (m_rpm > 0)
+        if (rpm() > 0)
         {
             setPwm(qMin(m_pwm * 0.95, m_pwm - 5.0));
             m_zeroRpm = 0;
@@ -355,8 +430,20 @@ void PwmFan::continueTest()
         break;
 
     case FindingStart:
-        if (m_rpm == 0)
-            setPwm(m_pwm + 2);
+        if (rpm() == 0)
+            if (m_pwm >= 255)
+            {
+                m_testStatus = Finished;
+                emit testStatusChanged();
+
+                m_zeroRpm = 0;
+                setMinStop(255);
+                setMinStart(255);
+
+                break;
+            }
+            else
+                setPwm(qMax(m_pwm + 2, 255));
         else
         {
             m_testStatus = FindingStop2;
@@ -367,7 +454,7 @@ void PwmFan::continueTest()
         break;
 
     case FindingStop2:
-        if (m_rpm > 0)
+        if (rpm() > 0)
         {
             setPwm(m_pwm - 1);
             m_zeroRpm = 0;
@@ -405,14 +492,14 @@ bool PwmFan::testing() const
 bool PwmFan::active() const
 {
     const auto active = KSharedConfig::openConfig(QStringLiteral("fancontrol-gui"))->group("active");
-    const auto localActive = active.group(m_parent->name());
+    const auto localActive = active.group(m_parent ? m_parent->name() : QStringLiteral(TEST_HWMON_NAME));
     return localActive.readEntry("pwmfan" + QString::number(m_index), true);
 }
 
 void PwmFan::setActive(bool a)
 {
     const auto active = KSharedConfig::openConfig(QStringLiteral("fancontrol-gui"))->group("active");
-    auto localActive = active.group(m_parent->name());
+    auto localActive = active.group(m_parent ? m_parent->name() : QStringLiteral(TEST_HWMON_NAME));
     if (a != localActive.readEntry("pwmfan" + QString::number(m_index), true))
     {
         localActive.writeEntry("pwmfan" + QString::number(m_index), a);

+ 11 - 6
import/src/pwmfan.h

@@ -66,7 +66,7 @@ public:
         Error
     };
 
-    explicit PwmFan(Hwmon *parent, uint index);
+    explicit PwmFan(uint index, Hwmon *parent = Q_NULLPTR);
     virtual ~PwmFan();
 
     int pwm() const Q_DECL_OVERRIDE { return m_pwm; }
@@ -87,13 +87,14 @@ public:
     void setHasTemp(bool hasTemp) { if (hasTemp != m_hasTemp) { m_hasTemp = hasTemp; emit hasTempChanged(); } }
     void setMinTemp(int minTemp) { if (minTemp != m_minTemp) { m_minTemp = minTemp; emit minTempChanged(); } }
     void setMaxTemp(int maxTemp) { if (maxTemp != m_maxTemp) { m_maxTemp = maxTemp; emit maxTempChanged(); } }
-    void setMinPwm(int minPwm) { if (minPwm != m_minPwm) { m_minPwm = minPwm; emit minPwmChanged(); } }
-    void setMaxPwm(int maxPwm) { if (maxPwm != m_maxPwm) { m_maxPwm = maxPwm; emit maxPwmChanged(); } }
+    void setMinPwm(int minPwm);
+    void setMaxPwm(int maxPwm);
     void setMinStart(int minStart) { if (minStart != m_minStart) { m_minStart = minStart; emit minStartChanged(); } }
     void setMinStop(int minStop) { if (minStop != m_minStop) { m_minStop = minStop; emit minStopChanged(); } }
     bool setPwmMode(int pwmMode, bool write = true);
     void setActive(bool active);
     void reset() Q_DECL_OVERRIDE;
+    bool isValid() const Q_DECL_OVERRIDE;
     Q_INVOKABLE void test();
     Q_INVOKABLE void abortTest();
 
@@ -120,11 +121,16 @@ public slots:
     void continueTest();
 
 
-private:
+protected:
 
-    int m_pwm;
     QTextStream *m_pwmStream;
     QTextStream *m_modeStream;
+
+
+private:
+
+    int m_pwm;
+    int m_pwmMode;
     Temp *m_temp;
     bool m_hasTemp;
     int m_minTemp;
@@ -133,7 +139,6 @@ private:
     int m_maxPwm;
     int m_minStart;
     int m_minStop;
-    int m_pwmMode;
     int m_zeroRpm;
     TestStatus m_testStatus;
 };

+ 2 - 1
import/src/sensor.cpp

@@ -31,7 +31,8 @@ Sensor::Sensor(Hwmon *parent, uint index, const QString &path) : QObject(parent)
     m_index(index),
     m_path(path)
 {
-    connect(this, &Sensor::error, parent, &Hwmon::error);
+    if (parent)
+        connect(this, &Sensor::error, parent, &Hwmon::error);
 }
 
 }

+ 2 - 1
import/src/sensor.h

@@ -40,11 +40,12 @@ class Sensor : public QObject
 
 public:
 
-    explicit Sensor(Hwmon *parent, uint index, const QString &path = QString());
+    explicit Sensor(Hwmon *parent = Q_NULLPTR, uint index = 0, const QString &path = QString());
 
     virtual QString name() const = 0;
     virtual void setName(const QString &name) = 0;
     virtual void reset() = 0;
+    virtual bool isValid() const = 0;
     QString path() const { return m_path; }
     Hwmon * parent() const { return m_parent; }
     uint index() const { return m_index; }

+ 1 - 2
import/src/systemdcommunicator.cpp

@@ -23,7 +23,6 @@
 #include "fancontrolaction.h"
 #include "guibase.h"
 
-#include <QtCore/QVariant>
 #include <QtCore/QTimer>
 #include <QtDBus/QDBusArgument>
 #include <QtDBus/QDBusInterface>
@@ -289,7 +288,7 @@ void SystemdCommunicator::handleDbusActionResult(KJob *job)
             {
                 const auto newJob = executeJob->action().execute();
                 connect(newJob, &KAuth::ExecuteJob::result, this, &SystemdCommunicator::handleDbusActionResult);
-                
+
                 QTimer::singleShot(50, newJob, &KAuth::ExecuteJob::start);
                 return;
             }

+ 9 - 6
import/src/systemdcommunicator.h

@@ -22,6 +22,9 @@
 #define SYSTEMDCOMMUNICATOR_H
 
 #include <QtCore/QObject>
+#include <QtCore/QString>
+#include <QtCore/QVariantMap>
+#include <QtCore/QVariantList>
 
 
 class QDBusInterface;
@@ -62,16 +65,16 @@ signals:
 
 
 protected slots:
-    
+
     void updateServiceProperties(QString, QVariantMap, QStringList);
     void handleDbusActionResult(KJob *job);
-    
-    
+
+
 protected:
-    
+
     bool dbusAction(const QString &method, const QVariantList &arguments = QVariantList());
-    
-    
+
+
 private:
 
     QString m_serviceName;

+ 40 - 26
import/src/temp.cpp

@@ -23,24 +23,28 @@
 
 #include "temp.h"
 
+#include "hwmon.h"
+
 #include <QtCore/QTextStream>
 #include <QtCore/QFile>
 #include <QtCore/QDir>
 
 #include <KConfigCore/KSharedConfig>
 #include <KConfigCore/KConfigGroup>
+#include <KI18n/KLocalizedString>
 
-#include "hwmon.h"
+
+#define TEST_HWMON_NAME "test"
 
 
 namespace Fancontrol
 {
 
-Temp::Temp(Hwmon *parent, uint index) :
-    Sensor(parent, index, QString(parent->name() + QStringLiteral("/temp") + QString::number(index))),
+Temp::Temp(uint index, Hwmon *parent) :
+    Sensor(parent, index, parent ? parent->name() + QStringLiteral("/temp") + QString::number(index) : QString()),
     m_valueStream(new QTextStream)
 {
-    if (QDir(parent->path()).isReadable())
+    if (parent && QDir(parent->path()).isReadable())
     {
         const auto valueFile = new QFile(parent->path() + "/temp" + QString::number(index) + "_input", this);
         const auto labelFile = new QFile(parent->path() + "/temp" + QString::number(index) + "_label");
@@ -54,7 +58,7 @@ Temp::Temp(Hwmon *parent, uint index) :
         else
         {
             delete valueFile;
-            emit error("Can't open valueFile " + parent->path() + "/temp" + QString::number(index) + "_input");
+            emit error(i18n("Can't open valueFile: %1", parent->path() + "/temp" + QString::number(index) + "_input"));
         }
 
         if (labelFile->exists())
@@ -62,10 +66,10 @@ Temp::Temp(Hwmon *parent, uint index) :
             if (labelFile->open(QFile::ReadOnly))
                 m_label = QTextStream(labelFile).readLine();
             else
-                emit error("Can't open labelFile: " + parent->path() + "/temp" + QString::number(index) + "_label");
+                emit error(i18n("Can't open labelFile: %1", parent->path() + "/temp" + QString::number(index) + "_label"));
         }
         else
-            emit error(parent->path() + "/temp" + QString::number(index) + "has no label.");
+            emit error(i18n("Temp has no label: %1", parent->path() + "/temp" + QString::number(index)));
 
         delete labelFile;
     }
@@ -73,21 +77,22 @@ Temp::Temp(Hwmon *parent, uint index) :
 
 Temp::~Temp()
 {
-    delete m_valueStream->device();
+    auto device = m_valueStream->device();
     delete m_valueStream;
+    delete device;
 }
 
 QString Temp::name() const
 {
     const auto names = KSharedConfig::openConfig(QStringLiteral("fancontrol-gui"))->group("names");
-    const auto localNames = names.group(m_parent->name());
+    const auto localNames = names.group(m_parent ? m_parent->name() : QStringLiteral(TEST_HWMON_NAME));
     const auto name = localNames.readEntry("temp" + QString::number(m_index), QString());
-    
+
     if (name.isEmpty())
     {
         if (m_label.isEmpty())
             return "temp" + QString::number(m_index);
-        
+
         return m_label;
     }
     return name;
@@ -96,33 +101,37 @@ QString Temp::name() const
 void Temp::setName(const QString &name)
 {
     const auto names = KSharedConfig::openConfig(QStringLiteral("fancontrol-gui"))->group("names");
-    auto localNames = names.group(m_parent->name());
+    auto localNames = names.group(m_parent ? m_parent->name() : QStringLiteral(TEST_HWMON_NAME));
 
     if (name != localNames.readEntry("temp" + QString::number(m_index), QString())
         && !name.isEmpty())
     {
-        localNames.writeEntry(m_parent->name() + "temp" + QString::number(m_index), name);
+        localNames.writeEntry("temp" + QString::number(m_index), name);
         emit nameChanged();
     }
 }
 
 void Temp::reset()
 {
-    delete m_valueStream->device();
-    delete m_valueStream;
-
-    if (QDir(m_parent->path()).isReadable())
+    if (m_valueStream->device() && m_parent)
     {
-        const auto valueFile = new QFile(m_parent->path() + "/temp" + QString::number(m_index) + "_input", this);
+        auto device = m_valueStream->device();
+        m_valueStream->setDevice(Q_NULLPTR);
+        delete device;
 
-        if (valueFile->open(QFile::ReadOnly))
+        if (QDir(m_parent->path()).isReadable())
         {
-            m_valueStream = new QTextStream(valueFile);
-            *m_valueStream >> m_value;
-            m_value /= 1000;
+            const auto valueFile = new QFile(m_parent->path() + "/temp" + QString::number(m_index) + "_input", this);
+
+            if (valueFile->open(QFile::ReadOnly))
+            {
+                m_valueStream->setDevice(valueFile);
+                *m_valueStream >> m_value;
+                m_value /= 1000;
+            }
+            else
+                emit error(i18n("Can't open valueFile: %1", m_parent->path() + "/temp" + QString::number(m_index) + "_input"));
         }
-        else
-            emit error("Can't open valueFile " + m_parent->path() + "/temp" + QString::number(m_index) + "_input");
     }
 }
 
@@ -134,8 +143,8 @@ void Temp::update()
     const auto value = m_valueStream->readAll().toInt(&success) / 1000;
 
     if (!success)
-        emit error("Can't update value of temp:" + m_parent->path() + "/temp" + QString::number(m_index));
-    
+        emit error(i18n("Can't update value of temp: %1", m_parent->path() + "/temp" + QString::number(m_index)));
+
     if (value != m_value)
     {
         m_value = value;
@@ -143,4 +152,9 @@ void Temp::update()
     }
 }
 
+bool Temp::isValid() const
+{
+    return m_valueStream->device() || m_valueStream->string();
+}
+
 }

+ 7 - 2
import/src/temp.h

@@ -41,7 +41,7 @@ class Temp : public Sensor
 
 public:
 
-    explicit Temp(Hwmon *parent, uint index);
+    explicit Temp(uint index, Hwmon *parent = Q_NULLPTR);
     virtual ~Temp();
 
     QString label() const { return m_label; }
@@ -49,6 +49,7 @@ public:
     QString name() const Q_DECL_OVERRIDE;
     void setName(const QString &name) Q_DECL_OVERRIDE;
     void reset() Q_DECL_OVERRIDE;
+    bool isValid() const Q_DECL_OVERRIDE;
 
 
 public slots:
@@ -62,11 +63,15 @@ signals:
     void valueChanged();
 
 
+protected:
+
+    QTextStream *m_valueStream;
+
+
 private:
 
     QString m_label;
     int m_value;
-    QTextStream *m_valueStream;
 };
 
 }

+ 47 - 0
import/tests/CMakeLists.txt

@@ -0,0 +1,47 @@
+find_package(Qt5 REQUIRED Test)
+
+include(ECMAddTests)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../src)
+
+set(TESTLIB_LIBS ${LIB_PRIVATE_LIBRARIES}
+                 ${LIB_PUBLIC_LIBRARIES})
+
+foreach(_src ${LIB_SRCS})
+    set(TESTLIB_SRCS ${TESTLIB_SRCS}
+                     "../${_src}")
+endforeach()
+
+set(TESTLIB_SRCS ${TESTLIB_SRCS}
+                 testfan.cpp
+                 testtemp.cpp
+                 testpwmfan.cpp)
+
+add_library(fancontrol_test_lib SHARED ${TESTLIB_SRCS})
+set_target_properties(fancontrol_test_lib PROPERTIES CXX_VISIBILITY_PRESET default)
+set_target_properties(fancontrol_test_lib PROPERTIES VISIBILITY_INLINES_HIDDEN OFF)
+target_link_libraries(fancontrol_test_lib PUBLIC ${TESTLIB_LIBS})
+
+set(TEST_LIBS Qt5::Test
+              fancontrol_test_lib)
+
+
+#temptest
+
+ecm_add_test(temptest.cpp LINK_LIBRARIES ${TEST_LIBS})
+
+
+#fantest
+
+ecm_add_test(fantest.cpp LINK_LIBRARIES ${TEST_LIBS})
+
+
+#pwmfantest
+
+ecm_add_test(pwmfantest.cpp LINK_LIBRARIES ${TEST_LIBS})
+
+
+#loadertest
+
+ecm_add_test(loadertest.cpp LINK_LIBRARIES ${TEST_LIBS})
+

+ 89 - 0
import/tests/fantest.cpp

@@ -0,0 +1,89 @@
+/*
+ * <one line to give the library's name and an idea of what it does.>
+ * Copyright 2015  <copyright holder> <email>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "fantest.h"
+
+#include <QtTest/QtTest>
+
+
+void FanTest::initTestCase()
+{
+    m_rpmString = QStringLiteral("500");
+    m_fan = new TestFan(&m_rpmString);
+}
+
+void FanTest::cleanupTestCase()
+{
+    delete m_fan;
+}
+
+void FanTest::init()
+{
+    // Called before each testfunction is executed
+}
+
+void FanTest::cleanup()
+{
+    // Called after every testfunction
+}
+
+void FanTest::nameTest_data()
+{
+    QTest::addColumn<QString>("name");
+
+    QTest::newRow("BigFan")  << "BigFan";
+    QTest::newRow("fan2")    << "fan2";
+}
+
+void FanTest::nameTest()
+{
+    QFETCH(QString, name);
+
+    m_fan->setName(name);
+
+    QCOMPARE(m_fan->name(), name);
+}
+
+void FanTest::rpmTest_data()
+{
+    QTest::addColumn<QString>("value");
+    QTest::addColumn<int>("result");
+
+    QTest::newRow("0")    << "0"     << 0;
+    QTest::newRow("100")  << "100"   << 100;
+    QTest::newRow("1000") << "1000"  << 1000;
+    QTest::newRow("5500") << "5500"  << 5500;
+}
+
+void FanTest::rpmTest()
+{
+    QFETCH(QString, value);
+    QFETCH(int, result);
+
+    m_rpmString = value;
+    m_fan->update();
+
+    QCOMPARE(m_fan->rpm(), result);
+}
+
+
+QTEST_MAIN(FanTest)

+ 19 - 15
tests/loadertest.h → import/tests/fantest.h

@@ -19,37 +19,41 @@
  *
  */
 
-#ifndef LOADERTEST_H
-#define LOADERTEST_H
+#ifndef FANTEST_H
+#define FANTEST_H
 
 #include <QtCore/QObject>
+#include <QtCore/QString>
 
-#include "lib/src/loader.h"
+#include "testfan.h"
 
 
 using namespace Fancontrol;
 
-class LoaderTest : public QObject
+
+class FanTest : public QObject
 {
     Q_OBJECT
-    
+
 private slots:
-  
+
     void initTestCase();
     void cleanupTestCase();
 
     void init();
     void cleanup();
 
-    void getEntryNumbersTest();
-    void getEntryNumbersTest_data();
-    void parseConfigLineTest();
-    void parseConfigLineTest_data();
-    
-    
+    void nameTest_data();
+    void nameTest();
+
+    void rpmTest_data();
+    void rpmTest();
+
+
 private:
-    
-    Loader *m_loader;
+
+    TestFan *m_fan;
+    QString m_rpmString;
 };
 
-#endif // LOADERTEST_H
+#endif // FANTEST_H

+ 478 - 0
import/tests/loadertest.cpp

@@ -0,0 +1,478 @@
+/*
+ * <one line to give the library's name and an idea of what it does.>
+ * Copyright 2015  <copyright holder> <email>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "loadertest.h"
+
+#include <QtTest/QtTest>
+#include <QtTest/QSignalSpy>
+
+
+void LoaderTest::initTestCase()
+{
+    m_loader = new TestLoader;
+
+    m_rpms << (QList<QString *>() << new QString << new QString << new QString);
+    m_rpms << (QList<QString *>() << new QString << new QString << new QString);
+
+    m_temps << (QList<QString *>() << new QString << new QString << new QString);
+    m_temps << (QList<QString *>() << new QString << new QString << new QString);
+
+    m_pwms << (QList<QString *>() << new QString << new QString);
+    m_pwms << (QList<QString *>() << new QString << new QString);
+
+    m_pwmModes << (QList<QString *>() << new QString << new QString);
+    m_pwmModes << (QList<QString *>() << new QString << new QString);
+
+    QCOMPARE(m_loader->hwmons().size(), 0);
+
+    m_loader->addHwmon(new TestHwmon("radeon", m_rpms.at(0), m_pwms.at(0), m_pwmModes.at(0), m_temps.at(0), 0, m_loader));
+    m_loader->addHwmon(new TestHwmon("coretemp", m_rpms.at(1), m_pwms.at(1), m_pwmModes.at(1), m_temps.at(1), 1, m_loader));
+
+    QCOMPARE(m_loader->hwmons().size(), 2);
+}
+
+void LoaderTest::cleanupTestCase()
+{
+    delete m_loader;
+}
+
+void LoaderTest::init()
+{
+    // Called before each testfunction is executed
+}
+
+void LoaderTest::cleanup()
+{
+    // Called after every testfunction
+}
+
+void LoaderTest::parseIntervalTest_data()
+{
+    QTest::addColumn<QString>("config");
+    QTest::addColumn<int>("result");
+    QTest::addColumn<QString>("error");
+    QTest::addColumn<bool>("critical");
+
+    QTest::newRow("valid4") << "INTERVAL=4" << 4 << "" << false;
+    QTest::newRow("valid2") << "INTERVAL=2" << 2 << "" << false;
+    QTest::newRow("valid2") << "INTERVAL= 3" << 3 << "" << false;
+    QTest::newRow("invalid0") << "INTERVAL=0" << 0 << "Interval must be greater or equal to one!" << true;
+    QTest::newRow("invalid6") << "INTERVA=6" << 6 << "Unrecognized line in config: \"INTERVA=6\"" << true;
+    QTest::newRow("invalid1") << "INTEVAL=1" << 1 << "Unrecognized line in config: \"INTEVAL=1\"" << true;
+}
+
+void LoaderTest::parseIntervalTest()
+{
+    QFETCH(QString, config);
+    QFETCH(int, result);
+    QFETCH(QString, error);
+    QFETCH(bool, critical);
+
+    QSignalSpy spy(m_loader, SIGNAL(error(QString, bool)));
+
+    m_loader->parse(config);
+
+    QCOMPARE(spy.isEmpty(), error.isEmpty());
+
+    if (spy.count())
+    {
+        QCOMPARE(spy.at(0).at(0).toString(), error);
+        QCOMPARE(spy.at(0).at(1).toBool(), critical);
+    }
+    else
+        QCOMPARE(m_loader->interval(), result);
+}
+
+void LoaderTest::parseFctempTest_data()
+{
+    QTest::addColumn<QString>("config");
+    QTest::addColumn<PwmFan *>("fan");
+    QTest::addColumn<Temp *>("temp");
+    QTest::addColumn<QString>("error");
+    QTest::addColumn<bool>("critical");
+
+    QTest::newRow("fan02temp11") << "FCTEMPS=hwmon0/fan2=hwmon1/temp1" << m_loader->hwmons().at(0)->pwmFan(1) << m_loader->hwmons().at(1)->temp(0) << "" << false;
+    QTest::newRow("fan11temp03") << "FCTEMPS=hwmon1/fan1=hwmon0/temp3" << m_loader->hwmons().at(1)->pwmFan(0) << m_loader->hwmons().at(0)->temp(2) << "" << false;
+    QTest::newRow("fan12temp12") << "FCTEMPS=hwmon1/fan2=hwmon1/temp2" << m_loader->hwmons().at(1)->pwmFan(1) << m_loader->hwmons().at(1)->temp(1) << "" << false;
+    QTest::newRow("invalid0") << "FCTEMPS=hwmon2/fan1=hwmon0/temp3" << static_cast<PwmFan *>(Q_NULLPTR) << m_loader->hwmons().at(0)->temp(2) << "Invalid fan entry: \"hwmon2/fan1\"" << true;
+    QTest::newRow("invalid1") << "FCTEMPS=hwmon0/fan1=hwmon0/temp4" << m_loader->hwmons().at(0)->pwmFan(0) << static_cast<Temp *>(Q_NULLPTR) << "Invalid temp entry: \"hwmon0/temp4\"" << true;
+}
+
+void LoaderTest::parseFctempTest()
+{
+    QFETCH(QString, config);
+    QFETCH(PwmFan *, fan);
+    QFETCH(Temp *, temp);
+    QFETCH(QString, error);
+    QFETCH(bool, critical);
+
+    QSignalSpy spy(m_loader, SIGNAL(error(QString, bool)));
+
+    m_loader->parse(config);
+
+    QCOMPARE(spy.isEmpty(), error.isEmpty());
+
+    if (fan)
+        QCOMPARE(fan->temp(), temp);
+
+    if (spy.count())
+    {
+        QCOMPARE(spy.at(0).at(0).toString(), error);
+        QCOMPARE(spy.at(0).at(1).toBool(), critical);
+    }
+    else
+        QCOMPARE(fan->hasTemp(), true);
+}
+
+void LoaderTest::parseDevnameTest_data()
+{
+    QTest::addColumn<QString>("config");
+    QTest::addColumn<QString>("error");
+    QTest::addColumn<bool>("critical");
+
+    QTest::newRow("valid0") << "DEVNAME=hwmon0=radeon" << "" << false;
+    QTest::newRow("valid1") << "DEVNAME=hwmon1=coretemp" << "" << false;
+    QTest::newRow("valid2") << "DEVNAME=hwmon0=radeon hwmon1=coretemp" << "" << false;
+    QTest::newRow("valid3") << "DEVNAME= hwmon1=coretemp hwmon0=radeon" << "" << false;
+    QTest::newRow("invalid0") << "DEVNAME=hwmon2=radeon" << "Invalid DEVNAME: \"hwmon2=radeon\"! No hwmon with index 2" << true;
+    QTest::newRow("invalid0") << "DEVNAME=hwmon1=radeon" << "Wrong name for hwmon 1! Should be \"coretemp\"" << true;
+}
+
+void LoaderTest::parseDevnameTest()
+{
+    QFETCH(QString, config);
+    QFETCH(QString, error);
+    QFETCH(bool, critical);
+
+    QSignalSpy spy(m_loader, SIGNAL(error(QString, bool)));
+
+    m_loader->parse(config);
+
+    QCOMPARE(spy.isEmpty(), error.isEmpty());
+
+    if (spy.count())
+    {
+        QCOMPARE(spy.at(0).at(0).toString(), error);
+        QCOMPARE(spy.at(0).at(1).toBool(), critical);
+    }
+}
+
+void LoaderTest::parseMintempTest_data()
+{
+    QTest::addColumn<QString>("config");
+    QTest::addColumn<PwmFan *>("fan");
+    QTest::addColumn<int>("result");
+    QTest::addColumn<QString>("error");
+    QTest::addColumn<bool>("critical");
+
+    QTest::newRow("valid01") << "MINTEMP=hwmon0/fan1=20" << m_loader->pwmFan(0, 0) << 20 << "" << false;
+    QTest::newRow("valid12") << "MINTEMP=hwmon1/fan2=35" << m_loader->pwmFan(1, 1) << 35 << "" << false;
+    QTest::newRow("valid02") << "MINTEMP=hwmon0/fan2=-35" << m_loader->pwmFan(0, 1) << -35 << "" << false;
+    QTest::newRow("valid11") << "MINTEMP= hwmon1/fan1=40" << m_loader->pwmFan(1, 0) << 40 << "" << false;
+}
+
+void LoaderTest::parseMintempTest()
+{
+    QFETCH(QString, config);
+    QFETCH(PwmFan *, fan);
+    QFETCH(int, result);
+    QFETCH(QString, error);
+    QFETCH(bool, critical);
+
+    QSignalSpy spy(m_loader, SIGNAL(error(QString, bool)));
+
+    m_loader->parse(config);
+
+    QCOMPARE(spy.isEmpty(), error.isEmpty());
+
+    if (spy.count())
+    {
+        QCOMPARE(spy.at(0).at(0).toString(), error);
+        QCOMPARE(spy.at(0).at(1).toBool(), critical);
+    }
+    else
+        QCOMPARE(fan->minTemp(), result);
+}
+
+void LoaderTest::parseMaxtempTest_data()
+{
+    QTest::addColumn<QString>("config");
+    QTest::addColumn<PwmFan *>("fan");
+    QTest::addColumn<int>("result");
+    QTest::addColumn<QString>("error");
+    QTest::addColumn<bool>("critical");
+
+    QTest::newRow("valid01") << "MAXTEMP=hwmon0/fan1=80" << m_loader->pwmFan(0, 0) << 80 << "" << false;
+    QTest::newRow("valid12") << "MAXTEMP=hwmon1/fan2=78 #iuf" << m_loader->pwmFan(1, 1) << 78 << "" << false;
+    QTest::newRow("valid02") << "MAXTEMP=hwmon0/fan2=-78" << m_loader->pwmFan(0, 1) << -78 << "" << false;
+    QTest::newRow("valid11") << "MAXTEMP= hwmon1/fan1=53" << m_loader->pwmFan(1, 0) << 53 << "" << false;
+}
+
+void LoaderTest::parseMaxtempTest()
+{
+    QFETCH(QString, config);
+    QFETCH(PwmFan *, fan);
+    QFETCH(int, result);
+    QFETCH(QString, error);
+    QFETCH(bool, critical);
+
+    QSignalSpy spy(m_loader, SIGNAL(error(QString, bool)));
+
+    m_loader->parse(config);
+
+    QCOMPARE(spy.isEmpty(), error.isEmpty());
+
+    if (spy.count())
+    {
+        QCOMPARE(spy.at(0).at(0).toString(), error);
+        QCOMPARE(spy.at(0).at(1).toBool(), critical);
+    }
+    else
+        QCOMPARE(fan->maxTemp(), result);
+}
+
+void LoaderTest::parseMinstartTest_data()
+{
+    QTest::addColumn<QString>("config");
+    QTest::addColumn<PwmFan *>("fan");
+    QTest::addColumn<int>("result");
+    QTest::addColumn<QString>("error");
+    QTest::addColumn<bool>("critical");
+
+    QTest::newRow("valid01") << "MINSTART=hwmon0/fan1=20" << m_loader->pwmFan(0, 0) << 20 << "" << false;
+    QTest::newRow("valid12") << "MINSTART=hwmon1/fan2=35" << m_loader->pwmFan(1, 1) << 35 << "" << false;
+    QTest::newRow("valid02") << "MINSTART=hwmon0/fan2=0#rtg" << m_loader->pwmFan(0, 1) << 0 << "" << false;
+    QTest::newRow("valid11") << "MINSTART= hwmon1/fan1=40" << m_loader->pwmFan(1, 0) << 40 << "" << false;
+}
+
+void LoaderTest::parseMinstartTest()
+{
+    QFETCH(QString, config);
+    QFETCH(PwmFan *, fan);
+    QFETCH(int, result);
+    QFETCH(QString, error);
+    QFETCH(bool, critical);
+
+    QSignalSpy spy(m_loader, SIGNAL(error(QString, bool)));
+
+    m_loader->parse(config);
+
+    QCOMPARE(spy.isEmpty(), error.isEmpty());
+
+    if (spy.count())
+    {
+        QCOMPARE(spy.at(0).at(0).toString(), error);
+        QCOMPARE(spy.at(0).at(1).toBool(), critical);
+    }
+    else
+        QCOMPARE(fan->minStart(), result);
+}
+
+void LoaderTest::parseMinstopTest_data()
+{
+    QTest::addColumn<QString>("config");
+    QTest::addColumn<PwmFan *>("fan");
+    QTest::addColumn<int>("result");
+    QTest::addColumn<QString>("error");
+    QTest::addColumn<bool>("critical");
+
+    QTest::newRow("valid01") << "MINSTOP=hwmon0/fan1=20" << m_loader->pwmFan(0, 0) << 20 << "" << false;
+    QTest::newRow("valid12") << "MINSTOP=hwmon1/fan2=35" << m_loader->pwmFan(1, 1) << 35 << "" << false;
+    QTest::newRow("valid02") << "MINSTOP=hwmon0/fan2=0" << m_loader->pwmFan(0, 1) << 0 << "" << false;
+    QTest::newRow("valid11") << "MINSTOP= hwmon1/fan1=40" << m_loader->pwmFan(1, 0) << 40 << "" << false;
+}
+
+void LoaderTest::parseMinstopTest()
+{
+    QFETCH(QString, config);
+    QFETCH(PwmFan *, fan);
+    QFETCH(int, result);
+    QFETCH(QString, error);
+    QFETCH(bool, critical);
+
+    QSignalSpy spy(m_loader, SIGNAL(error(QString, bool)));
+
+    m_loader->parse(config);
+
+    QCOMPARE(spy.isEmpty(), error.isEmpty());
+
+    if (spy.count())
+    {
+        QCOMPARE(spy.at(0).at(0).toString(), error);
+        QCOMPARE(spy.at(0).at(1).toBool(), critical);
+    }
+    else
+        QCOMPARE(fan->minStop(), result);
+}
+
+void LoaderTest::parseMinpwmTest_data()
+{
+    QTest::addColumn<QString>("config");
+    QTest::addColumn<PwmFan *>("fan");
+    QTest::addColumn<int>("result");
+    QTest::addColumn<QString>("error");
+    QTest::addColumn<bool>("critical");
+
+    QTest::newRow("valid01") << "MINPWM=hwmon0/fan1=20#fgiuh" << m_loader->pwmFan(0, 0) << 20 << "" << false;
+    QTest::newRow("valid12") << "MINPWM=hwmon1/fan2=35" << m_loader->pwmFan(1, 1) << 35 << "" << false;
+    QTest::newRow("valid02") << "MINPWM=hwmon0/fan2=0" << m_loader->pwmFan(0, 1) << 0 << "" << false;
+    QTest::newRow("valid11") << "MINPWM= hwmon1/fan1=40" << m_loader->pwmFan(1, 0) << 40 << "" << false;
+    QTest::newRow("invalid02") << "MINPWM=hwmon0/fan2=256" << m_loader->pwmFan(0, 1) << 256 << "MinPwm cannot exceed 0-255!" << true;
+    QTest::newRow("invalid11") << "MINPWM=hwmon1/fan2=-2" << m_loader->pwmFan(1, 1) << -2 << "MinPwm cannot exceed 0-255!" << true;
+}
+
+void LoaderTest::parseMinpwmTest()
+{
+    QFETCH(QString, config);
+    QFETCH(PwmFan *, fan);
+    QFETCH(int, result);
+    QFETCH(QString, error);
+    QFETCH(bool, critical);
+
+    QSignalSpy spy(m_loader, SIGNAL(error(QString, bool)));
+
+    m_loader->parse(config);
+
+    QCOMPARE(spy.isEmpty(), error.isEmpty());
+
+    if (spy.count())
+    {
+        QCOMPARE(spy.at(0).at(0).toString(), error);
+        QCOMPARE(spy.at(0).at(1).toBool(), critical);
+    }
+    else
+        QCOMPARE(fan->minPwm(), result);
+}
+
+void LoaderTest::parseMaxpwmTest_data()
+{
+    QTest::addColumn<QString>("config");
+    QTest::addColumn<PwmFan *>("fan");
+    QTest::addColumn<int>("result");
+    QTest::addColumn<QString>("error");
+    QTest::addColumn<bool>("critical");
+
+    QTest::newRow("valid01") << "MAXPWM=hwmon0/fan1=20" << m_loader->pwmFan(0, 0) << 20 << "" << false;
+    QTest::newRow("valid12") << "MAXPWM=hwmon1/fan2=35 #uivnriuhgfdn" << m_loader->pwmFan(1, 1) << 35 << "" << false;
+    QTest::newRow("valid02") << "MAXPWM=hwmon0/fan2=0" << m_loader->pwmFan(0, 1) << 0 << "" << false;
+    QTest::newRow("valid11") << "MAXPWM= hwmon1/fan1=40" << m_loader->pwmFan(1, 0) << 40 << "" << false;
+    QTest::newRow("invalid02") << "MAXPWM=hwmon0/fan2=256" << m_loader->pwmFan(0, 1) << 256 << "MaxPwm cannot exceed 0-255!" << true;
+    QTest::newRow("invalid11") << "MAXPWM=hwmon1/fan2=-2" << m_loader->pwmFan(1, 1) << -2 << "MaxPwm cannot exceed 0-255!" << true;
+}
+
+void LoaderTest::parseMaxpwmTest()
+{
+    QFETCH(QString, config);
+    QFETCH(PwmFan *, fan);
+    QFETCH(int, result);
+    QFETCH(QString, error);
+    QFETCH(bool, critical);
+
+    QSignalSpy spy(m_loader, SIGNAL(error(QString, bool)));
+
+    m_loader->parse(config);
+
+    QCOMPARE(spy.isEmpty(), error.isEmpty());
+
+    if (spy.count())
+    {
+        QCOMPARE(spy.at(0).at(0).toString(), error);
+        QCOMPARE(spy.at(0).at(1).toBool(), critical);
+    }
+    else
+        QCOMPARE(fan->maxPwm(), result);
+}
+
+void LoaderTest::parseUnrecognizableLineTest_data()
+{
+    QTest::addColumn<QString>("config");
+    QTest::addColumn<QString>("error");
+    QTest::addColumn<bool>("critical");
+
+    QTest::newRow("valid01") << "MMAXPWM=hwmon0/fan1=20" << "Unrecognized line in config: \"MMAXPWM=hwmon0/fan1=20\"" << true;
+}
+
+void LoaderTest::parseUnrecognizableLineTest()
+{
+    QFETCH(QString, config);
+    QFETCH(QString, error);
+    QFETCH(bool, critical);
+
+    QSignalSpy spy(m_loader, SIGNAL(error(QString, bool)));
+
+    m_loader->parse(config);
+
+    QCOMPARE(spy.count(), 1);
+
+    if (spy.count())
+    {
+        QCOMPARE(spy.at(0).at(0).toString(), error);
+        QCOMPARE(spy.at(0).at(1).toBool(), critical);
+    }
+}
+
+void LoaderTest::createConfigTest()
+{
+    auto pwmFan = m_loader->pwmFan(0, 0);
+    pwmFan->setTemp(m_loader->temp(1, 0));
+    pwmFan->setMinTemp(20);
+    pwmFan->setMaxTemp(60);
+    pwmFan->setMaxPwm(200);
+    pwmFan->setMinPwm(100);
+    pwmFan->setMinStart(120);
+    pwmFan->setMinStop(80);
+
+    pwmFan = m_loader->pwmFan(1, 1);
+    pwmFan->setTemp(m_loader->temp(1, 2));
+    pwmFan->setMinTemp(30);
+    pwmFan->setMaxTemp(70);
+    pwmFan->setMaxPwm(255);
+    pwmFan->setMinPwm(120);
+    pwmFan->setMinStart(110);
+    pwmFan->setMinStop(75);
+
+    m_loader->setInterval(5);
+
+    auto config = m_loader->createConfig();
+    QString expectedConfig = "# This file was created by Fancontrol-GUI\n"
+                             "INTERVAL=5\n"
+                             "DEVPATH=hwmon0= hwmon1= \n"
+                             "DEVNAME=hwmon0=radeon hwmon1=coretemp \n"
+                             "FCTEMPS=hwmon0/pwm0=hwmon1/temp0_input hwmon1/pwm1=hwmon1/temp2_input \n"
+                             "FCFANS=hwmon0/pwm0=hwmon0/fan0_input hwmon1/pwm1=hwmon1/fan1_input \n"
+                             "MINTEMP=hwmon0/pwm0=20 hwmon1/pwm1=30 \n"
+                             "MAXTEMP=hwmon0/pwm0=60 hwmon1/pwm1=70 \n"
+                             "MINSTART=hwmon0/pwm0=120 hwmon1/pwm1=110 \n"
+                             "MINSTOP=hwmon0/pwm0=80 hwmon1/pwm1=75 \n"
+                             "MINPWM=hwmon0/pwm0=100 hwmon1/pwm1=120 \n"
+                             "MAXPWM=hwmon0/pwm0=200 hwmon1/pwm1=255 \n";
+
+    auto expectedLines = expectedConfig.split(QChar(QChar::LineFeed));
+    auto configLines = config.split(QChar(QChar::LineFeed));
+
+    QCOMPARE(configLines.size(), expectedLines.size());
+
+    for (auto i=0; i<configLines.size(); i++)
+        QCOMPARE(configLines.at(i), expectedLines.at(i));
+}
+
+QTEST_MAIN(LoaderTest);

+ 176 - 0
import/tests/loadertest.h

@@ -0,0 +1,176 @@
+/*
+ * Copyright 2015  Malte Veerman maldela@halloarsch.de
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef LOADERTEST_H
+#define LOADERTEST_H
+
+#include <QtCore/QObject>
+
+#include "loader.h"
+#include "hwmon.h"
+#include "temptest.h"
+#include "fantest.h"
+#include "pwmfantest.h"
+
+
+using namespace Fancontrol;
+
+class TestHwmon : public Hwmon
+{
+    Q_OBJECT
+
+public:
+
+    explicit TestHwmon(const QString &name, const QList<QString *> &rpms, const QList<QString *> &pwms, const QList<QString *> &pwmmodes, const QList<QString *> &temps, uint index = 0, Loader *parent = Q_NULLPTR) : Hwmon(QString(), parent)
+    {
+        m_name = name;
+        m_index = index;
+
+        if (parent)
+        {
+            connect(this, &Hwmon::configUpdateNeeded, parent, &Loader::updateConfig);
+            connect(this, &Hwmon::error, parent, &Loader::error);
+        }
+
+        if (pwms.size() != pwmmodes.size())
+            return;
+
+        if (pwms.size() > rpms.size())
+            return;
+
+        for (auto i=0; i<pwms.size(); i++)
+        {
+            const auto newPwmFan = new TestPwmFan(pwms.at(i), pwmmodes.at(i), rpms.at(i), i, this);
+            connect(this, &Hwmon::sensorsUpdateNeeded, newPwmFan, &PwmFan::update);
+
+            if (parent)
+                connect(newPwmFan, &PwmFan::testStatusChanged, parent, &Loader::handleTestStatusChanged);
+
+            m_pwmFans << qobject_cast<PwmFan *>(newPwmFan);
+            m_fans << qobject_cast<Fan *>(newPwmFan);
+        }
+        emit pwmFansChanged();
+
+        for (auto i=m_pwmFans.size()-1; i<rpms.size(); i++)
+        {
+            const auto newFan = new TestFan(rpms.at(i), i, this);
+            connect(this, &Hwmon::sensorsUpdateNeeded, newFan, &Fan::update);
+
+            m_fans << qobject_cast<Fan *>(newFan);
+        }
+        emit fansChanged();
+
+        for (auto i=0; i<temps.size(); i++)
+        {
+            const auto newTemp = new TestTemp(temps.at(i), i, this);
+            connect(this, &Hwmon::sensorsUpdateNeeded, newTemp, &Temp::update);
+
+            m_temps << qobject_cast<Temp *>(newTemp);
+        }
+        emit tempsChanged();
+
+        m_valid = true;
+    }
+};
+
+class TestLoader : public Loader
+{
+    Q_OBJECT
+
+public:
+
+    explicit TestLoader(GUIBase *parent = Q_NULLPTR) : Loader(parent) {}
+
+    void addHwmon(Hwmon *hwmon)
+    {
+        connect(this, &Loader::sensorsUpdateNeeded, hwmon, &Hwmon::sensorsUpdateNeeded);
+        m_hwmons << hwmon;
+        emit hwmonsChanged();
+    }
+    void addHwmon(TestHwmon *hwmon)
+    {
+        addHwmon(qobject_cast<Hwmon *>(hwmon));
+    }
+    void parse(const QString &string)
+    {
+        parseConfig(string);
+    }
+    QString createConfig() const
+    {
+        return Loader::createConfig();
+    }
+};
+
+class LoaderTest : public QObject
+{
+    Q_OBJECT
+
+private slots:
+
+    void initTestCase();
+    void cleanupTestCase();
+
+    void init();
+    void cleanup();
+
+    void parseIntervalTest_data();
+    void parseIntervalTest();
+
+    void parseFctempTest_data();
+    void parseFctempTest();
+
+    void parseDevnameTest_data();
+    void parseDevnameTest();
+
+    void parseMintempTest_data();
+    void parseMintempTest();
+
+    void parseMaxtempTest_data();
+    void parseMaxtempTest();
+
+    void parseMinstartTest_data();
+    void parseMinstartTest();
+
+    void parseMinstopTest_data();
+    void parseMinstopTest();
+
+    void parseMinpwmTest_data();
+    void parseMinpwmTest();
+
+    void parseMaxpwmTest_data();
+    void parseMaxpwmTest();
+
+    void parseUnrecognizableLineTest_data();
+    void parseUnrecognizableLineTest();
+
+    void createConfigTest();
+
+
+private:
+
+    TestLoader *m_loader;
+    QList<QList<QString *> > m_rpms;
+    QList<QList<QString *> > m_pwms;
+    QList<QList<QString *> > m_temps;
+    QList<QList<QString *> > m_pwmModes;
+};
+
+#endif // LOADERTEST_H

+ 140 - 0
import/tests/pwmfantest.cpp

@@ -0,0 +1,140 @@
+/*
+ * <one line to give the library's name and an idea of what it does.>
+ * Copyright 2015  <copyright holder> <email>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "pwmfantest.h"
+
+#include <QtTest/QtTest>
+#include <QtTest/QSignalSpy>
+
+
+void PwmFanTest::initTestCase()
+{
+    m_pwmString = QStringLiteral("0");
+    m_modeString = QStringLiteral("0");
+    m_fan = new TestPwmFan(&m_pwmString, &m_modeString, Q_NULLPTR);
+}
+
+void PwmFanTest::cleanupTestCase()
+{
+    delete m_fan;
+}
+
+void PwmFanTest::init()
+{
+    // Called before each testfunction is executed
+}
+
+void PwmFanTest::cleanup()
+{
+    // Called after every testfunction
+}
+
+void PwmFanTest::nameTest_data()
+{
+    QTest::addColumn<QString>("name");
+
+    QTest::newRow("SmallFan")  << "SmallFan";
+    QTest::newRow("fan4")      << "fan4";
+}
+
+void PwmFanTest::nameTest()
+{
+    QFETCH(QString, name);
+
+    m_fan->setName(name);
+
+    QCOMPARE(m_fan->name(), name);
+}
+
+void PwmFanTest::pwmTest_data()
+{
+    QTest::addColumn<int>("value");
+    QTest::addColumn<QString>("error");
+
+    QTest::newRow("0")    << 0    << "";
+    QTest::newRow("100")  << 100  << "";
+    QTest::newRow("255")  << 255  << "";
+    QTest::newRow("-1")   << -1   << "Pwm cannot exceed 0-255!";
+    QTest::newRow("256")  << 256  << "Pwm cannot exceed 0-255!";
+}
+
+void PwmFanTest::pwmTest()
+{
+    QFETCH(int, value);
+    QFETCH(QString, error);
+
+    QSignalSpy spy(m_fan, SIGNAL(error(QString,bool)));
+
+    m_fan->setPwm(value);
+
+    if (error.isEmpty())
+        QCOMPARE(m_fan->pwm(), value);
+    else
+    {
+        QCOMPARE(spy.at(0).at(0).toString(), error);
+        QCOMPARE(spy.at(0).at(1).toBool(), true);
+    }
+}
+
+void PwmFanTest::modeTest_data()
+{
+    QTest::addColumn<int>("value");
+    QTest::addColumn<QString>("error");
+
+    QTest::newRow("0")   <<  0  << "";
+    QTest::newRow("1")   <<  1  << "";
+    QTest::newRow("2")   <<  2  << "";
+    QTest::newRow("3")   <<  3  << "PwmMode cannot exceed 0-2!";
+    QTest::newRow("-1")  << -1  << "PwmMode cannot exceed 0-2!";
+}
+
+void PwmFanTest::modeTest()
+{
+    QFETCH(int, value);
+    QFETCH(QString, error);
+
+    QSignalSpy spy(m_fan, SIGNAL(error(QString,bool)));
+
+    m_fan->setPwmMode(value);
+
+    if (error.isEmpty())
+        QCOMPARE(m_fan->pwmMode(), value);
+    else
+    {
+        QCOMPARE(spy.at(0).at(0).toString(), error);
+        QCOMPARE(spy.at(0).at(1).toBool(), true);
+    }
+}
+
+void PwmFanTest::activeTest()
+{
+    m_fan->setActive(false);
+
+    QCOMPARE(m_fan->active(), false);
+
+    m_fan->setActive(true);
+
+    QCOMPARE(m_fan->active(), true);
+}
+
+
+QTEST_MAIN(PwmFanTest)

+ 62 - 0
import/tests/pwmfantest.h

@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015  Malte Veerman maldela@halloarsch.de
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef PWMFANTEST_H
+#define PWMFANTEST_H
+
+#include <QtCore/QObject>
+#include <QtCore/QString>
+
+#include "testpwmfan.h"
+
+
+class PwmFanTest : public QObject
+{
+    Q_OBJECT
+
+private slots:
+
+    void initTestCase();
+    void cleanupTestCase();
+
+    void init();
+    void cleanup();
+
+    void nameTest_data();
+    void nameTest();
+
+    void pwmTest_data();
+    void pwmTest();
+
+    void modeTest_data();
+    void modeTest();
+
+    void activeTest();
+
+
+private:
+
+    TestPwmFan *m_fan;
+    QString m_pwmString;
+    QString m_modeString;
+};
+
+#endif // PWMFANTEST_H

+ 89 - 0
import/tests/temptest.cpp

@@ -0,0 +1,89 @@
+/*
+ * <one line to give the library's name and an idea of what it does.>
+ * Copyright 2015  <copyright holder> <email>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "temptest.h"
+
+#include <QtTest/QtTest>
+
+
+void TempTest::initTestCase()
+{
+    m_tempString = QStringLiteral("25");
+    m_temp = new TestTemp(&m_tempString);
+}
+
+void TempTest::cleanupTestCase()
+{
+    delete m_temp;
+}
+
+void TempTest::init()
+{
+    // Called before each testfunction is executed
+}
+
+void TempTest::cleanup()
+{
+    // Called after every testfunction
+}
+
+void TempTest::nameTest_data()
+{
+    QTest::addColumn<QString>("name");
+
+    QTest::newRow("radeon") << "radeon";
+    QTest::newRow("cpu")    << "cpu";
+}
+
+void TempTest::nameTest()
+{
+    QFETCH(QString, name);
+
+    m_temp->setName(name);
+
+    QCOMPARE(m_temp->name(), name);
+}
+
+void TempTest::valueTest_data()
+{
+    QTest::addColumn<QString>("value");
+    QTest::addColumn<int>("result");
+
+    QTest::newRow("0")   << "0"      << 0;
+    QTest::newRow("10")  << "10000"  << 10;
+    QTest::newRow("100") << "100000" << 100;
+    QTest::newRow("55")  << "55000"  << 55;
+}
+
+void TempTest::valueTest()
+{
+    QFETCH(QString, value);
+    QFETCH(int, result);
+
+    m_tempString = value;
+    m_temp->update();
+
+    QCOMPARE(m_temp->value(), result);
+}
+
+
+QTEST_MAIN(TempTest)

+ 56 - 0
import/tests/temptest.h

@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015  Malte Veerman maldela@halloarsch.de
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef TEMPTEST_H
+#define TEMPTEST_H
+
+#include <QtCore/QObject>
+#include <QtCore/QString>
+
+#include "testtemp.h"
+
+
+class TempTest : public QObject
+{
+    Q_OBJECT
+
+private slots:
+
+    void initTestCase();
+    void cleanupTestCase();
+
+    void init();
+    void cleanup();
+
+    void nameTest_data();
+    void nameTest();
+
+    void valueTest_data();
+    void valueTest();
+
+
+private:
+
+    Temp *m_temp;
+    QString m_tempString;
+};
+
+#endif // TEMPTEST_H

+ 30 - 0
import/tests/testfan.cpp

@@ -0,0 +1,30 @@
+/*
+ * <one line to give the program's name and a brief idea of what it does.>
+ * Copyright 2016  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "testfan.h"
+
+
+TestFan::TestFan(QString *rpmString, uint index, Hwmon *parent) : Fan(index, parent)
+{
+    if (rpmString)
+        m_rpmStream->setString(rpmString, QIODevice::ReadOnly);
+}

+ 44 - 0
import/tests/testfan.h

@@ -0,0 +1,44 @@
+/*
+ * <one line to give the program's name and a brief idea of what it does.>
+ * Copyright 2016  Malte Veerman <maldela@halloarsch.de>
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ * 
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+#ifndef TESTFAN_H
+#define TESTFAN_H
+
+
+#include "fan.h"
+
+#include <QtCore/QTextStream>
+
+
+using namespace Fancontrol;
+
+
+class TestFan : public Fan
+{
+    Q_OBJECT
+    
+public:
+    
+    explicit TestFan(QString *rpmString, uint index = 0, Hwmon *parent = Q_NULLPTR);
+};
+
+#endif // TESTFAN_H

+ 36 - 0
import/tests/testpwmfan.cpp

@@ -0,0 +1,36 @@
+/*
+ * <one line to give the program's name and a brief idea of what it does.>
+ * Copyright 2016  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "testpwmfan.h"
+
+
+TestPwmFan::TestPwmFan(QString *pwmString, QString *modeString, QString *rpmString, uint index, Hwmon *parent) : PwmFan(index, parent)
+{
+    if (pwmString)
+        m_pwmStream->setString(pwmString);
+
+    if (modeString)
+        m_modeStream->setString(modeString);
+
+    if (rpmString)
+        m_rpmStream->setString(rpmString);
+}

+ 44 - 0
import/tests/testpwmfan.h

@@ -0,0 +1,44 @@
+/*
+ * <one line to give the program's name and a brief idea of what it does.>
+ * Copyright 2016  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef TESTPWMFAN_H
+#define TESTPWMFAN_H
+
+
+#include <QtCore/QTextStream>
+
+#include "pwmfan.h"
+
+
+using namespace Fancontrol;
+
+
+class TestPwmFan : public PwmFan
+{
+    Q_OBJECT
+
+public:
+
+    explicit TestPwmFan(QString *pwmString, QString *modeString, QString *rpmString, uint index = 0, Hwmon *parent = Q_NULLPTR);
+};
+
+#endif // TESTPWMFAN_H

+ 30 - 0
import/tests/testtemp.cpp

@@ -0,0 +1,30 @@
+/*
+ * <one line to give the program's name and a brief idea of what it does.>
+ * Copyright 2016  Malte Veerman <maldela@halloarsch.de>
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ * 
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+#include "testtemp.h"
+
+
+TestTemp::TestTemp(QString *tempString, uint index, Hwmon *parent) : Temp(index, parent)
+{
+    if (tempString)
+        m_valueStream->setString(tempString);
+}

+ 42 - 0
import/tests/testtemp.h

@@ -0,0 +1,42 @@
+/*
+ * <one line to give the program's name and a brief idea of what it does.>
+ * Copyright 2016  Malte Veerman <maldela@halloarsch.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef TESTTEMP_H
+#define TESTTEMP_H
+
+#include <QtCore/QTextStream>
+
+#include "temp.h"
+
+
+using namespace Fancontrol;
+
+class TestTemp : public Temp
+{
+    Q_OBJECT
+
+public:
+
+    explicit TestTemp(QString *tempString, uint index = 0, Hwmon *parent = Q_NULLPTR);
+};
+
+#endif // TESTTEMP_H

+ 1 - 1
kcm/CMakeLists.txt

@@ -14,4 +14,4 @@ install(FILES kcm_fancontrol.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR})
 
 kpackage_install_package(package kcm_fancontrol kcms)
 
-kcoreaddons_desktop_to_json(kcm_fancontrol "kcm_fancontrol.desktop")
+kcoreaddons_desktop_to_json(kcm_fancontrol "kcm_fancontrol.desktop" SERVICE_TYPES kcmodule.desktop)

+ 3 - 5
kcm/kcm_fancontrol.desktop

@@ -2,13 +2,11 @@
 Exec=kcmshell5 kcm_fancontrol
 Icon=fancontrol_gui
 Type=Service
- 
+
 X-KDE-ServiceTypes=KCModule
 X-KDE-Library=kcm_fancontrol
-X-KDE-ParentApp=kcontrol
-X-KDE-Keywords=Fans,PWM,Temperature
+X-KDE-PluginKeyword=Fans
 X-KDE-System-Settings-Parent-Category=hardware
- 
+
 Name=Fancontrol
 Comment=Control PWM-fans
-Categories=Qt;KDE;X-KDE-settings-hardware;

+ 0 - 5
tests/CMakeLists.txt

@@ -1,5 +0,0 @@
-find_package(Qt5 REQUIRED Test)
-
-include(ECMAddTests)
-
-ecm_add_tests(loadertest.cpp LINK_LIBRARIES Qt5::Test fancontrol_gui_lib)

+ 0 - 90
tests/loadertest.cpp

@@ -1,90 +0,0 @@
-/*
- * <one line to give the library's name and an idea of what it does.>
- * Copyright 2015  <copyright holder> <email>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License or (at your option) version 3 or any later version
- * accepted by the membership of KDE e.V. (or its successor approved
- * by the membership of KDE e.V.), which shall act as a proxy
- * defined in Section 14 of version 3 of the license.
- *
- * This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include "loadertest.h"
-
-#include <QtTest/QtTest>
-
-
-#define COMMA ,
-
-
-QTEST_MAIN(LoaderTest);
-
-
-void LoaderTest::initTestCase()
-{
-    m_loader = new Loader;
-}
-
-void LoaderTest::cleanupTestCase()
-{
-    delete m_loader;
-}
-
-void LoaderTest::init()
-{
-    // Called before each testfunction is executed
-}
-
-void LoaderTest::cleanup()
-{
-    // Called after every testfunction
-}
-
-void LoaderTest::getEntryNumbersTest_data()
-{
-    QTest::addColumn<QString>("entry");
-    QTest::addColumn<QPair<int, int> >("result");
-    
-    QTest::newRow("valid1")     << "hwmon0/temp1"       << QPair<int, int>(0, 0);
-    QTest::newRow("valid2")     << "hwmon1/pwm2"        << QPair<int, int>(1, 1);
-    QTest::newRow("valid3")     << "hwmon2/temp8_input" << QPair<int, int>(2, 7);
-    QTest::newRow("valid4")     << "hwmon3/fan1"        << QPair<int, int>(3, 0);
-    QTest::newRow("invalid1")   << "hwmo0/temp1"        << QPair<int, int>(-1, -1);
-    QTest::newRow("invalid2")   << "hwmonn0/temp1"      << QPair<int, int>(-1, -1);
-    QTest::newRow("invalid3")   << "hwmon0/1"           << QPair<int, int>(-1, -1);
-    QTest::newRow("invalid4")   << "hwmon0/pwmfan1"     << QPair<int, int>(-1, -1);
-    QTest::newRow("invalid5")   << "hwmon0/fan1/temp3"  << QPair<int, int>(-1, -1);
-}
-
-void LoaderTest::getEntryNumbersTest()
-{
-    QFETCH(QString, entry);
-    QFETCH(QPair<int COMMA int>, result);
-    
-    QCOMPARE(Loader::getEntryNumbers(entry), result);
-}
-
-void LoaderTest::parseConfigLineTest_data()
-{
-    QTest::addColumn<QString>("line");
-    QTest::addColumn<int>("result");
-}
-
-void LoaderTest::parseConfigLineTest()
-{
-
-}
-
-
-#include "loadertest.moc"