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