File indexing completed on 2024-05-19 04:32:40

0001 /*
0002  *  SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #ifndef TEST_UTIL
0008 #define TEST_UTIL
0009 
0010 #include <QProcessEnvironment>
0011 
0012 #include <simpletest.h>
0013 #include <QList>
0014 #include <QTime>
0015 #include <QDir>
0016 
0017 #include <KoResource.h>
0018 #include <KoTestConfig.h>
0019 #include <KoColorSpace.h>
0020 #include <KoColorSpaceRegistry.h>
0021 #include <KoColorProfile.h>
0022 #include <KoProgressProxy.h>
0023 #include <kis_paint_device.h>
0024 #include <kis_node.h>
0025 #include <kis_undo_adapter.h>
0026 #include "kis_node_graph_listener.h"
0027 #include "kis_iterator_ng.h"
0028 #include "kis_image.h"
0029 #include "testing_nodes.h"
0030 
0031 #ifndef FILES_DATA_DIR
0032 #define FILES_DATA_DIR "."
0033 #endif
0034 
0035 #ifndef FILES_DEFAULT_DATA_DIR
0036 #define FILES_DEFAULT_DATA_DIR "."
0037 #endif
0038 
0039 #include "qimage_test_util.h"
0040 
0041 /**
0042  * Compare values and return false on failure
0043  * (normal QCOMPARE returns void)
0044  */
0045 #define KIS_COMPARE_RF(expr, ref) \
0046     if ((expr) != (ref)) { \
0047         qDebug() << "Compared values are not the same at line" << __LINE__; \
0048         qDebug() << "    Actual  : " << #expr << "=" << (expr); \
0049         qDebug() << "    Expected: " << #ref << "=" << (ref); \
0050         return false; \
0051     }
0052 
0053 /**
0054  * Compare two float numbers by rounding them up to \p prec
0055  * decimals after the point.
0056  */
0057 #define KIS_COMPARE_FLT(actual, expected, prec) \
0058 do {\
0059         const qreal multiplier = pow(10, prec); \
0060         if (!QTest::qCompare(qRound(actual * multiplier) / multiplier, qRound(expected * multiplier) / multiplier, #actual, #expected, __FILE__, __LINE__))\
0061         return;\
0062 } while (false)
0063 
0064 
0065 /**
0066  * Routines that are useful for writing efficient tests
0067  */
0068 
0069 namespace TestUtil
0070 {
0071 
0072 inline KisNodeSP findNode(KisNodeSP root, const QString &name) {
0073     if(root->name() == name) return root;
0074 
0075     KisNodeSP child = root->firstChild();
0076     while (child) {
0077         if((root = findNode(child, name))) return root;
0078         child = child->nextSibling();
0079     }
0080 
0081     return KisNodeSP();
0082 }
0083 
0084 inline void dumpNodeStack(KisNodeSP node, QString prefix = QString("\t"))
0085 {
0086     qDebug() << node->name();
0087     KisNodeSP child = node->firstChild();
0088 
0089     while (child) {
0090 
0091         if (child->childCount() > 0) {
0092             dumpNodeStack(child, prefix + "\t");
0093         } else {
0094             qDebug() << prefix << child->name();
0095         }
0096         child = child->nextSibling();
0097     }
0098 }
0099 
0100 class TestProgressBar : public KoProgressProxy {
0101 public:
0102     TestProgressBar()
0103         : m_min(0), m_max(0), m_value(0)
0104     {}
0105 
0106     int maximum() const override {
0107         return m_max;
0108     }
0109     void setValue(int value) override {
0110         m_value = value;
0111     }
0112     void setRange(int min, int max) override {
0113         m_min = min;
0114         m_max = max;
0115     }
0116     void setFormat(const QString &format) override {
0117         m_format = format;
0118     }
0119 
0120     void setAutoNestedName(const QString &name) override {
0121         m_autoNestedName = name;
0122         KoProgressProxy::setAutoNestedName(name);
0123     }
0124 
0125     int min() { return m_min; }
0126     int max() { return m_max; }
0127     int value() { return m_value; }
0128     QString format() { return m_format; }
0129     QString autoNestedName() { return m_autoNestedName; }
0130 
0131 
0132 private:
0133     int m_min;
0134     int m_max;
0135     int m_value;
0136     QString m_format;
0137     QString m_autoNestedName;
0138 };
0139 
0140 inline bool comparePaintDevices(QPoint & pt, const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2)
0141 {
0142     //     QTime t;
0143     //     t.start();
0144 
0145     QRect rc1 = dev1->exactBounds();
0146     QRect rc2 = dev2->exactBounds();
0147 
0148     if (rc1 != rc2) {
0149         pt.setX(-1);
0150         pt.setY(-1);
0151     }
0152 
0153     KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width());
0154     KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width());
0155 
0156     int pixelSize = dev1->pixelSize();
0157 
0158     for (int y = 0; y < rc1.height(); ++y) {
0159 
0160         do {
0161             if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0)
0162                 return false;
0163         } while (iter1->nextPixel() && iter2->nextPixel());
0164 
0165         iter1->nextRow();
0166         iter2->nextRow();
0167     }
0168     //     qDebug() << "comparePaintDevices time elapsed:" << t.elapsed();
0169     return true;
0170 }
0171 
0172 template <typename channel_type>
0173 inline bool comparePaintDevicesClever(const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2, channel_type alphaThreshold = 0)
0174 {
0175     QRect rc1 = dev1->exactBounds();
0176     QRect rc2 = dev2->exactBounds();
0177 
0178     if (rc1 != rc2) {
0179         qDebug() << "Devices have different size" << ppVar(rc1) << ppVar(rc2);
0180         return false;
0181     }
0182 
0183     KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width());
0184     KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width());
0185 
0186     int pixelSize = dev1->pixelSize();
0187 
0188     for (int y = 0; y < rc1.height(); ++y) {
0189 
0190         do {
0191             if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0) {
0192                 const channel_type* p1 = reinterpret_cast<const channel_type*>(iter1->oldRawData());
0193                 const channel_type* p2 = reinterpret_cast<const channel_type*>(iter2->oldRawData());
0194 
0195                 if (p1[3] < alphaThreshold && p2[3] < alphaThreshold) continue;
0196 
0197                 qDebug() << "Failed compare paint devices:" << iter1->x() << iter1->y();
0198                 qDebug() << "src:" << p1[0] << p1[1] << p1[2] << p1[3];
0199                 qDebug() << "dst:" << p2[0] << p2[1] << p2[2] << p2[3];
0200                 return false;
0201             }
0202         } while (iter1->nextPixel() && iter2->nextPixel());
0203 
0204         iter1->nextRow();
0205         iter2->nextRow();
0206     }
0207 
0208     return true;
0209 }
0210 
0211 #ifdef FILES_OUTPUT_DIR
0212 
0213 struct ReferenceImageChecker
0214 {
0215     enum StorageType {
0216         InternalStorage = 0,
0217         ExternalStorage
0218     };
0219 
0220     ReferenceImageChecker(const QString &prefix, const QString &testName, StorageType storageType = ExternalStorage)
0221         : m_storageType(storageType),
0222           m_prefix(prefix),
0223           m_testName(testName),
0224           m_success(true),
0225           m_maxFailingPixels(100),
0226           m_fuzzy(1)
0227         {
0228         }
0229 
0230 
0231     void setMaxFailingPixels(int value) {
0232         m_maxFailingPixels = value;
0233     }
0234 
0235     void setFuzzy(int fuzzy){
0236         m_fuzzy = fuzzy;
0237     }
0238 
0239     bool testPassed() const {
0240         return m_success;
0241     }
0242 
0243     inline bool checkDevice(KisPaintDeviceSP device, KisImageSP image, const QString &caseName) {
0244         bool result = false;
0245 
0246 
0247         if (m_storageType == ExternalStorage) {
0248             result = checkQImageExternal(device->convertToQImage(0, image->bounds()),
0249                                          m_testName,
0250                                          m_prefix,
0251                                          caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels);
0252         } else {
0253             result = checkQImage(device->convertToQImage(0, image->bounds()),
0254                                  m_testName,
0255                                  m_prefix,
0256                                  caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels);
0257         }
0258 
0259         m_success &= result;
0260         return result;
0261     }
0262 
0263     inline bool checkImage(KisImageSP image, const QString &testName) {
0264         bool result = checkDevice(image->projection(), image, testName);
0265 
0266         m_success &= result;
0267         return result;
0268     }
0269 
0270 private:
0271     bool m_storageType;
0272 
0273     QString m_prefix;
0274     QString m_testName;
0275 
0276     bool m_success;
0277     int m_maxFailingPixels;
0278     int m_fuzzy;
0279 };
0280 
0281 
0282 #endif
0283 
0284 inline quint8 alphaDevicePixel(KisPaintDeviceSP dev, qint32 x, qint32 y)
0285 {
0286     KisHLineConstIteratorSP iter = dev->createHLineConstIteratorNG(x, y, 1);
0287     const quint8 *pix = iter->oldRawData();
0288     return *pix;
0289 }
0290 
0291 inline void alphaDeviceSetPixel(KisPaintDeviceSP dev, qint32 x, qint32 y, quint8 s)
0292 {
0293     KisHLineIteratorSP iter = dev->createHLineIteratorNG(x, y, 1);
0294     quint8 *pix = iter->rawData();
0295     *pix = s;
0296 }
0297 
0298 inline bool checkAlphaDeviceFilledWithPixel(KisPaintDeviceSP dev, const QRect &rc, quint8 expected)
0299 {
0300     KisHLineIteratorSP it = dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width());
0301 
0302     for (int y = rc.y(); y < rc.y() + rc.height(); y++) {
0303         for (int x = rc.x(); x < rc.x() + rc.width(); x++) {
0304 
0305             if(*((quint8*)it->rawData()) != expected) {
0306                 errKrita << "At point:" << x << y;
0307                 errKrita << "Expected pixel:" << expected;
0308                 errKrita << "Actual pixel:  " << *((quint8*)it->rawData());
0309                 return false;
0310             }
0311             it->nextPixel();
0312         }
0313         it->nextRow();
0314     }
0315     return true;
0316 }
0317 
0318 class TestNode : public DefaultNode
0319 {
0320     Q_OBJECT
0321 public:
0322     KisNodeSP clone() const override {
0323         return KisNodeSP(new TestNode(*this));
0324     }
0325 };
0326 
0327 class TestGraphListener : public KisNodeGraphListener
0328 {
0329 public:
0330 
0331     void aboutToAddANode(KisNode *parent, int index) override {
0332         KisNodeGraphListener::aboutToAddANode(parent, index);
0333         beforeInsertRow = true;
0334     }
0335 
0336     void nodeHasBeenAdded(KisNode *parent, int index) override {
0337         KisNodeGraphListener::nodeHasBeenAdded(parent, index);
0338         afterInsertRow = true;
0339     }
0340 
0341     void aboutToRemoveANode(KisNode *parent, int index) override {
0342         KisNodeGraphListener::aboutToRemoveANode(parent, index);
0343         beforeRemoveRow  = true;
0344     }
0345 
0346     void nodeHasBeenRemoved(KisNode *parent, int index) override {
0347         KisNodeGraphListener::nodeHasBeenRemoved(parent, index);
0348         afterRemoveRow = true;
0349     }
0350 
0351     void aboutToMoveNode(KisNode *parent, int oldIndex, int newIndex) override {
0352         KisNodeGraphListener::aboutToMoveNode(parent, oldIndex, newIndex);
0353         beforeMove = true;
0354     }
0355 
0356     void nodeHasBeenMoved(KisNode *parent, int oldIndex, int newIndex) override {
0357         KisNodeGraphListener::nodeHasBeenMoved(parent, oldIndex, newIndex);
0358         afterMove = true;
0359     }
0360 
0361     bool beforeInsertRow;
0362     bool afterInsertRow;
0363     bool beforeRemoveRow;
0364     bool afterRemoveRow;
0365     bool beforeMove;
0366     bool afterMove;
0367 
0368     void resetBools() {
0369         beforeRemoveRow = false;
0370         afterRemoveRow = false;
0371         beforeInsertRow = false;
0372         afterInsertRow = false;
0373         beforeMove = false;
0374         afterMove = false;
0375     }
0376 };
0377 
0378 }
0379 
0380 #include <QApplication>
0381 #include <kis_paint_layer.h>
0382 #include "kis_undo_stores.h"
0383 #include "kis_layer_utils.h"
0384 
0385 namespace TestUtil {
0386 
0387 struct MaskParent
0388 {
0389     MaskParent(const QRect &_imageRect = QRect(0,0,512,512))
0390         : imageRect(_imageRect) {
0391         const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
0392         undoStore = new KisSurrogateUndoStore();
0393         image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "test image");
0394         layer = KisPaintLayerSP(new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8));
0395         image->addNode(KisNodeSP(layer.data()));
0396     }
0397 
0398     void waitForImageAndShapeLayers() {
0399         qApp->processEvents();
0400         image->waitForDone();
0401         KisLayerUtils::forceAllDelayedNodesUpdate(image->root());
0402         /**
0403          * Shape updates have two channels of compression, 100ms each.
0404          * One in KoShapeManager, the other one in KisShapeLayerCanvas.
0405          * Therefore we should wait for a decent amount of time for all
0406          * of them to land.
0407          */
0408 
0409         do {
0410             QTest::qWait(500);
0411         } while (!image->tryBarrierLock(true));
0412         image->unlock();
0413     }
0414 
0415     KisSurrogateUndoStore *undoStore;
0416     const QRect imageRect;
0417     KisImageSP image;
0418     KisPaintLayerSP layer;
0419 };
0420 
0421 }
0422 
0423 namespace TestUtil {
0424 
0425 class MeasureAvgPortion
0426 {
0427 public:
0428     MeasureAvgPortion(int period)
0429         : m_period(period),
0430         m_val(0),
0431         m_total(0),
0432         m_cycles(0)
0433     {
0434     }
0435 
0436     ~MeasureAvgPortion() {
0437         printValues(true);
0438     }
0439 
0440     void addVal(int x) {
0441         m_val += x;
0442     }
0443 
0444     void addTotal(int x) {
0445         m_total += x;
0446         m_cycles++;
0447         printValues();
0448     }
0449 
0450 private:
0451     void printValues(bool force = false) {
0452         if (m_cycles > m_period || force) {
0453             qDebug() << "Val / Total:" << qreal(m_val) / qreal(m_total);
0454             qDebug() << "Avg. Val:   " << qreal(m_val) / m_cycles;
0455             qDebug() << "Avg. Total: " << qreal(m_total) / m_cycles;
0456             qDebug() << ppVar(m_val) << ppVar(m_total) << ppVar(m_cycles);
0457 
0458             m_val = 0;
0459             m_total = 0;
0460             m_cycles = 0;
0461         }
0462     }
0463 
0464 private:
0465     int m_period;
0466     qint64 m_val;
0467     qint64 m_total;
0468     qint64 m_cycles;
0469 };
0470 
0471 struct MeasureDistributionStats {
0472     MeasureDistributionStats(int numBins, const QString &name = QString())
0473         : m_numBins(numBins),
0474           m_name(name)
0475     {
0476         reset();
0477     }
0478 
0479     void reset() {
0480         m_values.clear();
0481         m_values.resize(m_numBins);
0482     }
0483 
0484     void addValue(int value) {
0485         addValue(value, 1);
0486     }
0487 
0488     void addValue(int value, int increment) {
0489         KIS_SAFE_ASSERT_RECOVER_RETURN(value >= 0);
0490 
0491         if (value >= m_numBins) {
0492             m_values[m_numBins - 1] += increment;
0493         } else {
0494             m_values[value] += increment;
0495         }
0496     }
0497 
0498     void print() {
0499         qCritical() << "============= Stats ==============";
0500 
0501         if (!m_name.isEmpty()) {
0502             qCritical() << "Name:" << m_name;
0503         }
0504 
0505         int total = 0;
0506 
0507         for (int i = 0; i < m_numBins; i++) {
0508             total += m_values[i];
0509         }
0510 
0511         for (int i = 0; i < m_numBins; i++) {
0512             if (!m_values[i]) continue;
0513 
0514             const QString lastMarker = i == m_numBins - 1 ? "> " : "  ";
0515 
0516             const QString line =
0517                 QString("  %1%2: %3 (%4%)")
0518                     .arg(lastMarker)
0519                     .arg(i, 3)
0520                     .arg(m_values[i], 5)
0521                     .arg(qreal(m_values[i]) / total * 100.0, 7, 'g', 2);
0522 
0523             qCritical() << qPrintable(line);
0524         }
0525         qCritical() << "----                          ----";
0526         qCritical() << qPrintable(QString("Total: %1").arg(total));
0527         qCritical() << "==================================";
0528     }
0529 
0530 private:
0531     QVector<int> m_values;
0532     int m_numBins = 0;
0533     QString m_name;
0534 };
0535 
0536 QStringList getHierarchy(KisNodeSP root, const QString &prefix = "");
0537 bool checkHierarchy(KisNodeSP root, const QStringList &expected);
0538 
0539 }
0540 
0541 #endif