Skip to content

Commit 9932ae1

Browse files
committed
Backport test suite from master
1 parent 60b14a2 commit 9932ae1

File tree

254 files changed

+1216
-679
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

254 files changed

+1216
-679
lines changed

src/core/qgsrenderchecker.cpp

+96-86
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#include <QDebug>
2828
#include <QBuffer>
2929

30+
static int renderCounter = 0;
31+
3032
QgsRenderChecker::QgsRenderChecker()
3133
: mReport( "" )
3234
, mMatchTarget( 0 )
@@ -35,6 +37,8 @@ QgsRenderChecker::QgsRenderChecker()
3537
, mExpectedImageFile( "" )
3638
, mMismatchCount( 0 )
3739
, mColorTolerance( 0 )
40+
, mMaxSizeDifferenceX( 0 )
41+
, mMaxSizeDifferenceY( 0 )
3842
, mElapsedTimeTarget( 0 )
3943
, mBufferDashMessages( false )
4044
{
@@ -43,16 +47,14 @@ QgsRenderChecker::QgsRenderChecker()
4347
QString QgsRenderChecker::controlImagePath() const
4448
{
4549
QString myDataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
46-
QString myControlImageDir = myDataDir + QDir::separator() + "control_images" +
47-
QDir::separator() + mControlPathPrefix;
50+
QString myControlImageDir = myDataDir + "/control_images/" + mControlPathPrefix;
4851
return myControlImageDir;
4952
}
5053

5154
void QgsRenderChecker::setControlName( const QString &theName )
5255
{
5356
mControlName = theName;
54-
mExpectedImageFile = controlImagePath() + theName + QDir::separator() + mControlPathSuffix
55-
+ theName + ".png";
57+
mExpectedImageFile = controlImagePath() + theName + "/" + mControlPathSuffix + theName + ".png";
5658
}
5759

5860
QString QgsRenderChecker::imageToHash( QString theImageFile )
@@ -101,8 +103,7 @@ void QgsRenderChecker::drawBackground( QImage* image )
101103

102104
bool QgsRenderChecker::isKnownAnomaly( QString theDiffImageFile )
103105
{
104-
QString myControlImageDir = controlImagePath() + mControlName
105-
+ QDir::separator();
106+
QString myControlImageDir = controlImagePath() + mControlName + "/";
106107
QDir myDirectory = QDir( myControlImageDir );
107108
QStringList myList;
108109
QString myFilename = "*";
@@ -121,8 +122,7 @@ bool QgsRenderChecker::isKnownAnomaly( QString theDiffImageFile )
121122
mReport += "<tr><td colspan=3>"
122123
"Checking if " + myFile + " is a known anomaly.";
123124
mReport += "</td></tr>";
124-
QString myAnomalyHash = imageToHash( controlImagePath() + mControlName
125-
+ QDir::separator() + myFile );
125+
QString myAnomalyHash = imageToHash( controlImagePath() + mControlName + "/" + myFile );
126126
QString myHashMessage = QString(
127127
"Checking if anomaly %1 (hash %2)<br>" )
128128
.arg( myFile )
@@ -209,8 +209,7 @@ bool QgsRenderChecker::runTest( QString theTestName,
209209
// Save the pixmap to disk so the user can make a
210210
// visual assessment if needed
211211
//
212-
mRenderedImageFile = QDir::tempPath() + QDir::separator() +
213-
theTestName + "_result.png";
212+
mRenderedImageFile = QDir::tempPath() + "/" + theTestName + "_result.png";
214213

215214
myImage.setDotsPerMeterX( myExpectedImage.dotsPerMeterX() );
216215
myImage.setDotsPerMeterY( myExpectedImage.dotsPerMeterY() );
@@ -226,7 +225,7 @@ bool QgsRenderChecker::runTest( QString theTestName,
226225

227226
//create a world file to go with the image...
228227

229-
QFile wldFile( QDir::tempPath() + QDir::separator() + theTestName + "_result.wld" );
228+
QFile wldFile( QDir::tempPath() + "/" + theTestName + "_result.wld" );
230229
if ( wldFile.open( QIODevice::WriteOnly ) )
231230
{
232231
QgsRectangle r = mMapSettings.extent();
@@ -258,9 +257,14 @@ bool QgsRenderChecker::compareImages( QString theTestName,
258257
}
259258
if ( ! theRenderedImageFile.isEmpty() )
260259
{
260+
#ifndef Q_OS_WIN
261261
mRenderedImageFile = theRenderedImageFile;
262+
#else
263+
mRenderedImageFile = theRenderedImageFile.replace( "\\", "/" );
264+
#endif
262265
}
263-
else if ( mRenderedImageFile.isEmpty() )
266+
267+
if ( mRenderedImageFile.isEmpty() )
264268
{
265269
qDebug( "QgsRenderChecker::runTest failed - Rendered Image File not set." );
266270
mReport = "<table>"
@@ -269,6 +273,7 @@ bool QgsRenderChecker::compareImages( QString theTestName,
269273
"Image File not set.</td></tr></table>\n";
270274
return false;
271275
}
276+
272277
//
273278
// Load /create the images
274279
//
@@ -286,9 +291,7 @@ bool QgsRenderChecker::compareImages( QString theTestName,
286291
QImage myDifferenceImage( myExpectedImage.width(),
287292
myExpectedImage.height(),
288293
QImage::Format_RGB32 );
289-
QString myDiffImageFile = QDir::tempPath() +
290-
QDir::separator() +
291-
theTestName + "_result_diff.png";
294+
QString myDiffImageFile = QDir::tempPath() + "/" + theTestName + "_result_diff.png";
292295
myDifferenceImage.fill( qRgb( 152, 219, 249 ) );
293296

294297
//check for mask
@@ -310,20 +313,22 @@ bool QgsRenderChecker::compareImages( QString theTestName,
310313
//
311314
// Set the report with the result
312315
//
313-
mReport = "<table>";
316+
mReport = QString( "<script src=\"file://%1/../renderchecker.js\"></script>\n" ).arg( TEST_DATA_DIR );
317+
mReport += "<table>";
314318
mReport += "<tr><td colspan=2>";
315-
mReport += "Test image and result image for " + theTestName + "<br>"
316-
"Expected size: " + QString::number( myExpectedImage.width() ).toLocal8Bit() + "w x " +
317-
QString::number( myExpectedImage.height() ).toLocal8Bit() + "h (" +
318-
QString::number( mMatchTarget ).toLocal8Bit() + " pixels)<br>"
319-
"Actual size: " + QString::number( myResultImage.width() ).toLocal8Bit() + "w x " +
320-
QString::number( myResultImage.height() ).toLocal8Bit() + "h (" +
321-
QString::number( myPixelCount ).toLocal8Bit() + " pixels)";
322-
mReport += "</td></tr>";
323-
mReport += "<tr><td colspan = 2>\n";
324-
mReport += "Expected Duration : <= " + QString::number( mElapsedTimeTarget ) +
325-
"ms (0 indicates not specified)<br>";
326-
mReport += "Actual Duration : " + QString::number( mElapsedTime ) + "ms<br>";
319+
mReport += QString( "<tr><td colspan=2>"
320+
"Test image and result image for %1<br>"
321+
"Expected size: %2 w x %3 h (%4 pixels)<br>"
322+
"Actual size: %5 w x %6 h (%7 pixels)"
323+
"</td></tr>" )
324+
.arg( theTestName )
325+
.arg( myExpectedImage.width() ).arg( myExpectedImage.height() ).arg( mMatchTarget )
326+
.arg( myResultImage.width() ).arg( myResultImage.height() ).arg( myPixelCount );
327+
mReport += QString( "<tr><td colspan=2>\n"
328+
"Expected Duration : <= %1 (0 indicates not specified)<br>"
329+
"Actual Duration : %2 ms<br></td></tr>" )
330+
.arg( mElapsedTimeTarget )
331+
.arg( mElapsedTime );
327332

328333
// limit image size in page to something reasonable
329334
int imgWidth = 420;
@@ -333,21 +338,23 @@ bool QgsRenderChecker::compareImages( QString theTestName,
333338
imgWidth = qMin( myExpectedImage.width(), imgWidth );
334339
imgHeight = myExpectedImage.height() * imgWidth / myExpectedImage.width();
335340
}
336-
QString myImagesString = "</td></tr>"
337-
"<tr><td>Test Result:</td><td>Expected Result:</td><td>Difference (all blue is good, any red is bad)</td></tr>\n"
338-
"<tr><td><img width=" + QString::number( imgWidth ) +
339-
" height=" + QString::number( imgHeight ) +
340-
" src=\"file://" +
341-
mRenderedImageFile +
342-
"\"></td>\n<td><img width=" + QString::number( imgWidth ) +
343-
" height=" + QString::number( imgHeight ) +
344-
" src=\"file://" +
345-
mExpectedImageFile +
346-
"\"></td>\n<td><img width=" + QString::number( imgWidth ) +
347-
" height=" + QString::number( imgHeight ) +
348-
" src=\"file://" +
349-
myDiffImageFile +
350-
"\"></td>\n</tr>\n</table>";
341+
342+
QString myImagesString = QString(
343+
"<tr>"
344+
"<td colspan=2>Compare actual and expected result</td>"
345+
"<td>Difference (all blue is good, any red is bad)</td>"
346+
"</tr>\n<tr>"
347+
"<td colspan=2 id=\"td-%1-%7\"></td>\n"
348+
"<td align=center><img width=%5 height=%6 src=\"file://%2\"></td>\n"
349+
"</tr>"
350+
"</table>\n"
351+
"<script>\naddComparison(\"td-%1-%7\",\"file://%3\",\"file://%4\",%5,%6);\n</script>\n" )
352+
.arg( theTestName )
353+
.arg( myDiffImageFile )
354+
.arg( mRenderedImageFile )
355+
.arg( mExpectedImageFile )
356+
.arg( imgWidth ).arg( imgHeight )
357+
.arg( renderCounter++ );
351358

352359
QString prefix;
353360
if ( !mControlPathPrefix.isNull() )
@@ -369,30 +376,44 @@ bool QgsRenderChecker::compareImages( QString theTestName,
369376

370377
if ( mMatchTarget != myPixelCount )
371378
{
372-
qDebug( "Test image and result image for %s are different - FAILING!", theTestName.toLocal8Bit().constData() );
373-
mReport += "<tr><td colspan=3>";
374-
mReport += "<font color=red>Expected image and result image for " + theTestName + " are different dimensions - FAILING!</font>";
375-
mReport += "</td></tr>";
376-
mReport += myImagesString;
377-
delete maskImage;
378-
return false;
379+
qDebug( "Test image and result image for %s are different dimensions", theTestName.toLocal8Bit().constData() );
380+
381+
if ( qAbs( myExpectedImage.width() - myResultImage.width() ) > mMaxSizeDifferenceX ||
382+
qAbs( myExpectedImage.height() - myResultImage.height() ) > mMaxSizeDifferenceY )
383+
{
384+
mReport += "<tr><td colspan=3>";
385+
mReport += "<font color=red>Expected image and result image for " + theTestName + " are different dimensions - FAILING!</font>";
386+
mReport += "</td></tr>";
387+
mReport += myImagesString;
388+
delete maskImage;
389+
return false;
390+
}
391+
else
392+
{
393+
mReport += "<tr><td colspan=3>";
394+
mReport += "Expected image and result image for " + theTestName + " are different dimensions, but within tolerance";
395+
mReport += "</td></tr>";
396+
}
379397
}
380398

381399
//
382400
// Now iterate through them counting how many
383401
// dissimilar pixel values there are
384402
//
385403

404+
int maxHeight = qMin( myExpectedImage.height(), myResultImage.height() );
405+
int maxWidth = qMin( myExpectedImage.width(), myResultImage.width() );
406+
386407
mMismatchCount = 0;
387408
int colorTolerance = ( int ) mColorTolerance;
388-
for ( int y = 0; y < myExpectedImage.height(); ++y )
409+
for ( int y = 0; y < maxHeight; ++y )
389410
{
390411
const QRgb* expectedScanline = ( const QRgb* )myExpectedImage.constScanLine( y );
391412
const QRgb* resultScanline = ( const QRgb* )myResultImage.constScanLine( y );
392413
const QRgb* maskScanline = hasMask ? ( const QRgb* )maskImage->constScanLine( y ) : 0;
393414
QRgb* diffScanline = ( QRgb* )myDifferenceImage.scanLine( y );
394415

395-
for ( int x = 0; x < myExpectedImage.width(); ++x )
416+
for ( int x = 0; x < maxWidth; ++x )
396417
{
397418
int maskTolerance = hasMask ? qRed( maskScanline[ x ] ) : 0;
398419
int pixelTolerance = qMax( colorTolerance, maskTolerance );
@@ -440,40 +461,14 @@ bool QgsRenderChecker::compareImages( QString theTestName,
440461
//
441462
// Send match result to report
442463
//
443-
mReport += "<tr><td colspan=3>" +
444-
QString::number( mMismatchCount ) + "/" +
445-
QString::number( mMatchTarget ) +
446-
" pixels mismatched (allowed threshold: " +
447-
QString::number( theMismatchCount ) +
448-
", allowed color component tolerance: " +
449-
QString::number( mColorTolerance ) + ")";
450-
mReport += "</td></tr>";
464+
mReport += QString( "<tr><td colspan=3>%1/%2 pixels mismatched (allowed threshold: %3, allowed color component tolerance: %4)</td></tr>" )
465+
.arg( mMismatchCount ).arg( mMatchTarget ).arg( theMismatchCount ).arg( mColorTolerance );
451466

452467
//
453468
// And send it to CDash
454469
//
455470
emitDashMessage( "Mismatch Count", QgsDartMeasurement::Integer, QString( "%1/%2" ).arg( mMismatchCount ).arg( mMatchTarget ) );
456471

457-
bool myAnomalyMatchFlag = isKnownAnomaly( myDiffImageFile );
458-
459-
if ( myAnomalyMatchFlag )
460-
{
461-
mReport += "<tr><td colspan=3>"
462-
"Difference image matched a known anomaly - passing test! "
463-
"</td></tr>";
464-
return true;
465-
}
466-
else
467-
{
468-
mReport += "<tr><td colspan=3>"
469-
"</td></tr>";
470-
emitDashMessage( "No Anomalies Match", QgsDartMeasurement::Text, "Difference image did not match any known anomaly."
471-
" If you feel the difference image should be considered an anomaly "
472-
"you can do something like this\n"
473-
"cp " + myDiffImageFile + " ../tests/testdata/control_images/" + theTestName +
474-
"/<imagename>.{wld,png}" );
475-
}
476-
477472
if ( mMismatchCount <= theMismatchCount )
478473
{
479474
mReport += "<tr><td colspan = 3>\n";
@@ -495,12 +490,27 @@ bool QgsRenderChecker::compareImages( QString theTestName,
495490
return true;
496491
}
497492
}
498-
else
493+
494+
bool myAnomalyMatchFlag = isKnownAnomaly( myDiffImageFile );
495+
if ( myAnomalyMatchFlag )
499496
{
500-
mReport += "<tr><td colspan = 3>\n";
501-
mReport += "<font color=red>Test image and result image for " + theTestName + " are mismatched</font><br>";
502-
mReport += "</td></tr>";
503-
mReport += myImagesString;
504-
return false;
497+
mReport += "<tr><td colspan=3>"
498+
"Difference image matched a known anomaly - passing test! "
499+
"</td></tr>";
500+
return true;
505501
}
502+
503+
mReport += "<tr><td colspan=3></td></tr>";
504+
emitDashMessage( "Image mismatch", QgsDartMeasurement::Text, "Difference image did not match any known anomaly or mask."
505+
" If you feel the difference image should be considered an anomaly "
506+
"you can do something like this\n"
507+
"cp '" + myDiffImageFile + "' " + controlImagePath() + mControlName +
508+
"/\nIf it should be included in the mask run\n"
509+
"scripts/generate_test_mask_image.py '" + mExpectedImageFile + "' '" + mRenderedImageFile + "'\n" );
510+
511+
mReport += "<tr><td colspan = 3>\n";
512+
mReport += "<font color=red>Test image and result image for " + theTestName + " are mismatched</font><br>";
513+
mReport += "</td></tr>";
514+
mReport += myImagesString;
515+
return false;
506516
}

src/core/qgsrenderchecker.h

+17-6
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@ class CORE_EXPORT QgsRenderChecker
4141
QgsRenderChecker();
4242

4343
//! Destructor
44-
~QgsRenderChecker() {};
44+
~QgsRenderChecker() {}
4545

4646
QString controlImagePath() const;
4747

48-
QString report() { return mReport; };
48+
QString report() { return mReport; }
49+
4950
float matchPercent()
5051
{
5152
return static_cast<float>( mMismatchCount ) /
@@ -55,7 +56,7 @@ class CORE_EXPORT QgsRenderChecker
5556
unsigned int matchTarget() { return mMatchTarget; }
5657
//only records time for actual render part
5758
int elapsedTime() { return mElapsedTime; }
58-
void setElapsedTimeTarget( int theTarget ) { mElapsedTimeTarget = theTarget; };
59+
void setElapsedTimeTarget( int theTarget ) { mElapsedTimeTarget = theTarget; }
5960

6061
/** Base directory name for the control image (with control image path
6162
* suffixed) the path to the image will be constructed like this:
@@ -66,9 +67,9 @@ class CORE_EXPORT QgsRenderChecker
6667
/** Prefix where the control images are kept.
6768
* This will be appended to controlImagePath
6869
*/
69-
void setControlPathPrefix( const QString &theName ) { mControlPathPrefix = theName + QDir::separator(); }
70+
void setControlPathPrefix( const QString &theName ) { mControlPathPrefix = theName + "/"; }
7071

71-
void setControlPathSuffix( const QString& theName ) { mControlPathSuffix = theName + QDir::separator(); }
72+
void setControlPathSuffix( const QString& theName ) { mControlPathSuffix = theName + "/"; }
7273

7374
/** Get an md5 hash that uniquely identifies an image */
7475
QString imageToHash( QString theImageFile );
@@ -96,6 +97,14 @@ class CORE_EXPORT QgsRenderChecker
9697
* @note added in 2.1
9798
*/
9899
void setColorTolerance( unsigned int theColorTolerance ) { mColorTolerance = theColorTolerance; }
100+
101+
/** Sets the largest allowable difference in size between the rendered and the expected image.
102+
* @param xTolerance x tolerance in pixels
103+
* @param yTolerance y tolerance in pixels
104+
* @note added in QGIS 2.12
105+
*/
106+
void setSizeTolerance( int xTolerance, int yTolerance ) { mMaxSizeDifferenceX = xTolerance; mMaxSizeDifferenceY = yTolerance; }
107+
99108
/**
100109
* Test using renderer to generate the image to be compared.
101110
* @param theTestName - to be used as the basis for writing a file to
@@ -129,7 +138,7 @@ class CORE_EXPORT QgsRenderChecker
129138
*/
130139
bool isKnownAnomaly( QString theDiffImageFile );
131140

132-
/**Draws a checkboard pattern for image backgrounds, so that transparency is visible
141+
/** Draws a checkboard pattern for image backgrounds, so that transparency is visible
133142
* without requiring a transparent background for the image
134143
*/
135144
static void drawBackground( QImage* image );
@@ -173,6 +182,8 @@ class CORE_EXPORT QgsRenderChecker
173182
QString mControlName;
174183
unsigned int mMismatchCount;
175184
unsigned int mColorTolerance;
185+
int mMaxSizeDifferenceX;
186+
int mMaxSizeDifferenceY;
176187
int mElapsedTimeTarget;
177188
QgsMapSettings mMapSettings;
178189
QString mControlPathPrefix;

0 commit comments

Comments
 (0)