Skip to content

Commit b28dcb9

Browse files
committed
[unit tests] Add multirenderchecker
The multirenderchecker allows to have several images, each with its own set of anomalies distributed in several subdirectories. With the help of multiple reference images, it is possible to apply a color tolerance to each of these
1 parent 3528661 commit b28dcb9

File tree

9 files changed

+349
-35
lines changed

9 files changed

+349
-35
lines changed

python/core/core.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
%Include qgsmessagelog.sip
6767
%Include qgsmessageoutput.sip
6868
%Include qgsmimedatautils.sip
69+
%Include qgsmultirenderchecker.sip
6970
%Include qgsnetworkaccessmanager.sip
7071
%Include qgsnetworkcontentfetcher.sip
7172
%Include qgsofflineediting.sip
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/***************************************************************************
2+
qgsmultirenderchecker.sip
3+
--------------------------------------
4+
Date : 6.11.2014
5+
Copyright : (C) 2014 Matthias Kuhn
6+
Email : matthias dot kuhn at gmx dot ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
/**
17+
*
18+
* @note added in 2.8
19+
*/
20+
21+
class QgsMultiRenderChecker
22+
{
23+
%TypeHeaderCode
24+
#include <qgsmultirenderchecker.h>
25+
%End
26+
public:
27+
QgsMultiRenderChecker();
28+
29+
/**
30+
* Base directory name for the control image (with control image path
31+
* suffixed) the path to the image will be constructed like this:
32+
* controlImagePath + '/' + mControlName + '/' + mControlName + '.png'
33+
*/
34+
void setControlName( const QString& theName );
35+
36+
void setControlPathPrefix( const QString& prefix );
37+
38+
/**
39+
* Set the path to the rendered image. If this is not set or set to QString::Null, an image
40+
* will be rendered based on the provided mapsettings
41+
*
42+
* @param renderedImagePath A path to the rendered image with which control images will be compared
43+
*/
44+
void setRenderedImage( const QString& renderedImagePath );
45+
46+
/**
47+
* Set the map settings to use to render the image
48+
*
49+
* @param mapSettings The map settings
50+
*/
51+
void setMapSettings( const QgsMapSettings& mapSettings );
52+
53+
/**
54+
* Set tolerance for color components used by runTest()
55+
* Default value is 0.
56+
*
57+
* @param theColorTolerance The maximum difference for each color component
58+
* including alpha to be considered correct.
59+
*/
60+
void setColorTolerance( unsigned int theColorTolerance );
61+
62+
/**
63+
* Test using renderer to generate the image to be compared.
64+
*
65+
* @param theTestName - to be used as the basis for writing a file to
66+
* e.g. /tmp/theTestName.png
67+
*
68+
* @param theMismatchCount - defaults to 0 - the number of pixels that
69+
* are allowed to be different from the control image. In some cases
70+
* rendering may be non-deterministic. This parameter allows you to account
71+
* for that by providing a tolerance.
72+
*
73+
* @note make sure to call setExpectedImage and setMapSettings first
74+
*/
75+
bool runTest( const QString& theTestName, unsigned int theMismatchCount = 0 );
76+
77+
/**
78+
* Returns a report for this test
79+
*
80+
* @return A report
81+
*/
82+
const QString& report() const;
83+
84+
/**
85+
* @brief controlImagePath
86+
* @return
87+
*/
88+
const QString controlImagePath() const;
89+
90+
};
91+

src/core/CMakeLists.txt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,13 @@ SET(QGIS_CORE_SRCS
114114
qgsmapsettings.cpp
115115
qgsmaptopixel.cpp
116116
qgsmaptopixelgeometrysimplifier.cpp
117+
qgsmessagelog.cpp
117118
qgsmessageoutput.cpp
118119
qgsmimedatautils.cpp
119-
qgsmessagelog.cpp
120+
qgsmultirenderchecker.cpp
120121
qgsnetworkaccessmanager.cpp
121-
qgsnetworkreplyparser.cpp
122122
qgsnetworkcontentfetcher.cpp
123+
qgsnetworkreplyparser.cpp
123124
qgsobjectcustomproperties.cpp
124125
qgsofflineediting.cpp
125126
qgsogcutils.cpp
@@ -131,17 +132,17 @@ SET(QGIS_CORE_SRCS
131132
qgspoint.cpp
132133
qgsproject.cpp
133134
qgsprojectfiletransform.cpp
134-
qgsprojectversion.cpp
135135
qgsprojectproperty.cpp
136+
qgsprojectversion.cpp
136137
qgsprovidercountcalcevent.cpp
137138
qgsproviderextentcalcevent.cpp
138139
qgsprovidermetadata.cpp
139140
qgsproviderregistry.cpp
140141
qgspythonrunner.cpp
141142
qgsrelation.cpp
142143
qgsrelationmanager.cpp
143-
qgsrendercontext.cpp
144144
qgsrenderchecker.cpp
145+
qgsrendercontext.cpp
145146
qgsrectangle.cpp
146147
qgsrunprocess.cpp
147148
qgsscalecalculator.cpp
@@ -513,8 +514,9 @@ SET(QGIS_CORE_HDRS
513514
qgsmapunitscale.h
514515
qgsmessageoutput.h
515516
qgsmimedatautils.h
516-
qgsnetworkreplyparser.h
517+
qgsmultirenderchecker.h
517518
qgsnetworkcontentfetcher.h
519+
qgsnetworkreplyparser.h
518520
qgsobjectcustomproperties.h
519521
qgsofflineediting.h
520522
qgsogcutils.h
@@ -534,10 +536,10 @@ SET(QGIS_CORE_HDRS
534536
qgsproviderregistry.h
535537
qgspythonrunner.h
536538
qgsrectangle.h
537-
qgsrendercontext.h
538-
qgsrenderchecker.h
539539
qgsrelation.h
540540
qgsrelationmanager.h
541+
qgsrenderchecker.h
542+
qgsrendercontext.h
541543
qgsrunprocess.h
542544
qgsscalecalculator.h
543545
qgsscaleutils.h

src/core/qgsmultirenderchecker.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/***************************************************************************
2+
qgsmultirenderchecker.cpp
3+
--------------------------------------
4+
Date : 6.11.2014
5+
Copyright : (C) 2014 Matthias Kuhn
6+
Email : matthias dot kuhn at gmx dot ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsmultirenderchecker.h"
17+
18+
#include <QDebug>
19+
20+
QgsMultiRenderChecker::QgsMultiRenderChecker()
21+
{
22+
}
23+
24+
void QgsMultiRenderChecker::setControlName( const QString& theName )
25+
{
26+
mControlName = theName;
27+
}
28+
29+
void QgsMultiRenderChecker::setControlPathPrefix( const QString& prefix )
30+
{
31+
mControlPathPrefix = prefix;
32+
}
33+
34+
void QgsMultiRenderChecker::setMapSettings( const QgsMapSettings& mapSettings )
35+
{
36+
mMapSettings = mapSettings;
37+
}
38+
39+
bool QgsMultiRenderChecker::runTest( const QString& theTestName, unsigned int theMismatchCount )
40+
{
41+
bool successful = false;
42+
43+
const QString baseDir = controlImagePath();
44+
45+
QStringList subDirs = QDir( baseDir ).entryList( QDir::Dirs | QDir::NoDotAndDotDot );
46+
47+
if ( subDirs.count() == 0 )
48+
{
49+
subDirs << "";
50+
}
51+
52+
Q_FOREACH( const QString& suffix, subDirs )
53+
{
54+
qDebug() << "Checking subdir " << suffix;
55+
bool result;
56+
QgsRenderChecker checker;
57+
checker.setColorTolerance( mColorTolerance );
58+
checker.setControlPathPrefix( mControlPathPrefix );
59+
checker.setControlPathSuffix( suffix );
60+
checker.setControlName( mControlName );
61+
checker.setMapSettings( mMapSettings );
62+
63+
if ( !mRenderedImage.isNull() )
64+
{
65+
checker.setRenderedImage( mRenderedImage );
66+
result = checker.compareImages( theTestName, theMismatchCount, mRenderedImage );
67+
}
68+
else
69+
{
70+
result = checker.runTest( theTestName, theMismatchCount );
71+
mRenderedImage = checker.renderedImage();
72+
}
73+
74+
qDebug() << " * Subdir check " << suffix << ": " << result;
75+
successful |= result;
76+
77+
mReport += checker.report();
78+
}
79+
80+
if ( !successful )
81+
qDebug() << "No matching image found. If you think that this result should be considered ok, please copy it into a new subdirectory inside " << baseDir;
82+
83+
return successful;
84+
}
85+
86+
const QString QgsMultiRenderChecker::controlImagePath() const
87+
{
88+
QString myDataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
89+
QString myControlImageDir = myDataDir + QDir::separator() + "control_images" +
90+
QDir::separator() + mControlPathPrefix + QDir::separator() + mControlName + QDir::separator();
91+
return myControlImageDir;
92+
}

src/core/qgsmultirenderchecker.h

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/***************************************************************************
2+
qgsmultirenderchecker.h
3+
--------------------------------------
4+
Date : 6.11.2014
5+
Copyright : (C) 2014 Matthias Kuhn
6+
Email : matthias dot kuhn at gmx dot ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSMULTIRENDERCHECKER_H
17+
#define QGSMULTIRENDERCHECKER_H
18+
19+
#include "qgsrenderchecker.h"
20+
21+
/**
22+
* This class allows to check rendered images against comparison images.
23+
* Its main purpose is for the unit testing framework.
24+
*
25+
* It will either
26+
* <ul>
27+
* <li>take an externally rendered image (setRenderedImage())</li>
28+
* <li>render the image based on provided mapSettings (setMapSettings())</li>
29+
* </ul>
30+
*
31+
* This image will then be compared against one or several images in a folder inside
32+
* the control directory (tests/testdata/control_images/{controlName}).
33+
*
34+
* There are modes for single and for multiple reference images.
35+
* <ul>
36+
* <li>If there are no subfolders in the control directory, it will assume an image
37+
* with the name {controlImage}.png in the control directory itself.</li>
38+
*
39+
* <li>If there are subfolders inside the control directory, it will search for images
40+
* with the name {controlImage}.png in every subfolder.</li>
41+
* </ul>
42+
*
43+
* For every control image there may be one or several randomly named anomaly images defining
44+
* allowed anomalies.
45+
* For every control image, the allowed mismatch and color tolerance values will be calculated
46+
* individually.
47+
*
48+
* @note added in 2.8
49+
*/
50+
51+
class CORE_EXPORT QgsMultiRenderChecker
52+
{
53+
public:
54+
QgsMultiRenderChecker();
55+
56+
/**
57+
* Base directory name for the control image (with control image path
58+
* suffixed) the path to the image will be constructed like this:
59+
* controlImagePath + '/' + mControlName + '/' + mControlName + '.png'
60+
*/
61+
void setControlName( const QString& theName );
62+
63+
void setControlPathPrefix( const QString& prefix );
64+
65+
/**
66+
* Set the path to the rendered image. If this is not set or set to QString::Null, an image
67+
* will be rendered based on the provided mapsettings
68+
*
69+
* @param renderedImagePath A path to the rendered image with which control images will be compared
70+
*/
71+
void setRenderedImage( const QString& renderedImagePath ) { mRenderedImage = renderedImagePath; }
72+
73+
/**
74+
* Set the map settings to use to render the image
75+
*
76+
* @param mapSettings The map settings
77+
*/
78+
void setMapSettings( const QgsMapSettings& mapSettings );
79+
80+
/**
81+
* Set tolerance for color components used by runTest()
82+
* Default value is 0.
83+
*
84+
* @param theColorTolerance The maximum difference for each color component
85+
* including alpha to be considered correct.
86+
*/
87+
void setColorTolerance( unsigned int theColorTolerance ) { mColorTolerance = theColorTolerance; }
88+
89+
/**
90+
* Test using renderer to generate the image to be compared.
91+
*
92+
* @param theTestName - to be used as the basis for writing a file to
93+
* e.g. /tmp/theTestName.png
94+
*
95+
* @param theMismatchCount - defaults to 0 - the number of pixels that
96+
* are allowed to be different from the control image. In some cases
97+
* rendering may be non-deterministic. This parameter allows you to account
98+
* for that by providing a tolerance.
99+
*
100+
* @note make sure to call setExpectedImage and setMapSettings first
101+
*/
102+
bool runTest( const QString& theTestName, unsigned int theMismatchCount = 0 );
103+
104+
/**
105+
* Returns a report for this test
106+
*
107+
* @return A report
108+
*/
109+
const QString& report() const { return mReport; }
110+
111+
/**
112+
* @brief controlImagePath
113+
* @return
114+
*/
115+
const QString controlImagePath() const;
116+
117+
private:
118+
QString mReport;
119+
QString mRenderedImage;
120+
QString mControlName;
121+
QString mControlPathPrefix;
122+
unsigned int mColorTolerance;
123+
QgsMapSettings mMapSettings;
124+
};
125+
126+
#endif // QGSMULTIRENDERCHECKER_H

src/core/qgsrenderchecker.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ QString QgsRenderChecker::controlImagePath() const
5151
void QgsRenderChecker::setControlName( const QString theName )
5252
{
5353
mControlName = theName;
54-
mExpectedImageFile = controlImagePath() + theName + QDir::separator()
54+
mExpectedImageFile = controlImagePath() + theName + QDir::separator() + mControlPathSuffix
5555
+ theName + ".png";
5656
}
5757

0 commit comments

Comments
 (0)