27#include <QCryptographicHash>
28#include <QDeadlineTimer>
31#include <QtConcurrentMap>
33using namespace Qt::StringLiterals;
38static void getFeaturesForProvider(
const QPair< std::shared_ptr< QgsAbstractFeatureSource >,
QgsFeatureRequest > &pair )
49 constexpr int JOBS_TO_RUN = 100;
50 QList< QPair< std::shared_ptr< QgsAbstractFeatureSource >,
QgsFeatureRequest > > jobs;
51 jobs.reserve( JOBS_TO_RUN );
52 for (
int i = 0; i < JOBS_TO_RUN; ++i )
54 jobs.append( qMakePair( std::shared_ptr< QgsAbstractFeatureSource >( provider->
featureSource() ), request ) );
59 QtConcurrent::blockingMap( jobs, getFeaturesForProvider );
64bool QgsTestUtils::compareDomElements(
const QDomElement &element1,
const QDomElement &element2 )
66 QString tag1 = element1.tagName();
67 tag1.replace( QRegularExpression(
".*:" ), QString() );
68 QString tag2 = element2.tagName();
69 tag2.replace( QRegularExpression(
".*:" ), QString() );
72 qDebug(
"Different tag names: %s, %s", tag1.toLatin1().data(), tag2.toLatin1().data() );
76 if ( element1.hasAttributes() != element2.hasAttributes() )
78 qDebug(
"Different hasAttributes: %s, %s", tag1.toLatin1().data(), tag2.toLatin1().data() );
82 if ( element1.hasAttributes() )
84 const QDomNamedNodeMap attrs1 = element1.attributes();
85 const QDomNamedNodeMap attrs2 = element2.attributes();
87 if ( attrs1.size() != attrs2.size() )
89 qDebug(
"Different attributes size: %s, %s", tag1.toLatin1().data(), tag2.toLatin1().data() );
93 for (
int i = 0; i < attrs1.size(); ++i )
95 const QDomNode node1 = attrs1.item( i );
96 const QDomAttr attr1 = node1.toAttr();
98 if ( !element2.hasAttribute( attr1.name() ) )
100 qDebug(
"Element2 has not attribute: %s, %s, %s", tag1.toLatin1().data(), tag2.toLatin1().data(), attr1.name().toLatin1().data() );
104 if ( element2.attribute( attr1.name() ) != attr1.value() )
106 qDebug(
"Element2 attribute has not the same value: %s, %s, %s", tag1.toLatin1().data(), tag2.toLatin1().data(), attr1.name().toLatin1().data() );
112 if ( element1.hasChildNodes() != element2.hasChildNodes() )
114 qDebug(
"Different childNodes: %s, %s", tag1.toLatin1().data(), tag2.toLatin1().data() );
118 if ( element1.hasChildNodes() )
120 const QDomNodeList nodes1 = element1.childNodes();
121 const QDomNodeList nodes2 = element2.childNodes();
123 if ( nodes1.size() != nodes2.size() )
125 qDebug(
"Different childNodes size: %s, %s", tag1.toLatin1().data(), tag2.toLatin1().data() );
129 for (
int i = 0; i < nodes1.size(); ++i )
131 const QDomNode node1 = nodes1.at( i );
132 const QDomNode node2 = nodes2.at( i );
133 if ( node1.isElement() && node2.isElement() )
135 QDomElement elt1 = node1.toElement();
136 QDomElement elt2 = node2.toElement();
138 if ( !compareDomElements( elt1, elt2 ) )
141 else if ( node1.isText() && node2.isText() )
143 const QDomText txt1 = node1.toText();
144 const QDomText txt2 = node2.toText();
146 if ( txt1.data() != txt2.data() )
148 qDebug(
"Different text data: %s %s", tag1.toLatin1().data(), txt1.data().toLatin1().data() );
149 qDebug(
"Different text data: %s %s", tag2.toLatin1().data(), txt2.data().toLatin1().data() );
156 if ( element1.text() != element2.text() )
158 qDebug(
"Different text: %s %s", tag1.toLatin1().data(), element1.text().toLatin1().data() );
159 qDebug(
"Different text: %s %s", tag2.toLatin1().data(), element2.text().toLatin1().data() );
166QString QgsTestUtils::sanitizeFakeHttpEndpoint(
const QString &urlString )
168 QString modifiedUrlString = urlString;
171 const int afterEndpointStartPos =
static_cast<int>( modifiedUrlString.indexOf(
"fake_qgis_http_endpoint" ) + strlen(
"fake_qgis_http_endpoint" ) );
172 QString afterEndpointStart = modifiedUrlString.mid( afterEndpointStartPos );
173 afterEndpointStart.replace(
"/"_L1,
"_"_L1 );
174 modifiedUrlString = modifiedUrlString.mid( 0, afterEndpointStartPos ) + afterEndpointStart;
176 const qsizetype posQuotationMark = modifiedUrlString.indexOf(
'?' );
177 QString args = modifiedUrlString.mid( posQuotationMark );
178 if ( modifiedUrlString.size() > 256 )
180 args.replace(
'/',
'_' );
181 args = QCryptographicHash::hash( args.toUtf8(), QCryptographicHash::Md5 ).toHex();
185 args.replace(
'?',
'_' );
186 args.replace(
'&',
'_' );
187 args.replace(
'<',
'_' );
188 args.replace(
'>',
'_' );
189 args.replace(
'\"',
'_' );
190 args.replace(
'\'',
'_' );
191 args.replace(
' ',
'_' );
192 args.replace(
':',
'_' );
193 args.replace(
'/',
'_' );
194 args.replace(
'\n',
'_' );
196 return modifiedUrlString.mid( 0, posQuotationMark ) + args;
204 class LayerOpenWorker :
public QThread
207 LayerOpenWorker(
const QString &uri,
const QString &providerKey, std::atomic<bool> &stop )
209 , mProviderKey( providerKey )
218 const QgsVectorLayer layer( mUri, u
"concurrent_access_worker"_s, mProviderKey );
219 if ( !layer.isValid() )
223 layer.getFeatures().nextFeature( f );
227 int loops()
const {
return mLoops; }
228 int validOpens()
const {
return mValidOpens; }
232 QString mProviderKey;
233 std::atomic<bool> &mStop;
242 class SublayerQueryWorker :
public QThread
245 SublayerQueryWorker( QgsProviderMetadata *metadata,
const QString &uri,
int expectedSublayerCount, std::atomic<bool> &stop )
246 : mMetadata( metadata )
248 , mExpectedSublayerCount( expectedSublayerCount )
257 if ( mMetadata->querySublayers( mUri ).size() == mExpectedSublayerCount )
258 mConsistentQueries++;
262 int loops()
const {
return mLoops; }
263 int consistentQueries()
const {
return mConsistentQueries; }
266 QgsProviderMetadata *mMetadata =
nullptr;
268 int mExpectedSublayerCount = 0;
269 std::atomic<bool> &mStop;
271 int mConsistentQueries = 0;
276bool QgsTestUtils::testConcurrentLayerAccess(
const QString &uri,
const QString &providerKey,
int runTimeMs,
int timeoutMs )
278 constexpr int LAYER_WORKER_COUNT = 6;
283 qDebug(
"testConcurrentLayerAccess: no provider metadata found for %s", providerKey.toLatin1().constData() );
289 const bool expectValidLayer =
QgsVectorLayer( uri, u
"concurrent_access_baseline"_s, providerKey ).
isValid();
290 const int expectedSublayerCount =
static_cast<int>( metadata->
querySublayers( uri ).size() );
292 std::atomic<bool> stop(
false );
293 std::vector<std::unique_ptr<QThread>> threads;
295 std::vector<LayerOpenWorker *> layerWorkers;
296 for (
int i = 0; i < LAYER_WORKER_COUNT; i++ )
298 layerWorkers.push_back(
new LayerOpenWorker( uri, providerKey, stop ) );
299 threads.emplace_back( layerWorkers.back() );
302 SublayerQueryWorker *sublayerWorker =
new SublayerQueryWorker( metadata, uri, expectedSublayerCount, stop );
303 threads.emplace_back( sublayerWorker );
305 for (
auto &thread : threads )
309 QThread::msleep(
static_cast<unsigned long>( runTimeMs ) );
315 QDeadlineTimer deadline( timeoutMs );
316 bool allFinished =
true;
317 for (
auto &thread : threads )
318 allFinished = thread->wait( deadline ) && allFinished;
325 for (
auto &thread : threads )
326 ( void ) thread.release();
328 "testConcurrentLayerAccess: deadlock detected, not all threads "
329 "accessing %s finished in time",
330 uri.toLatin1().constData()
336 for (
const LayerOpenWorker *worker : layerWorkers )
338 if ( worker->loops() == 0 )
341 "testConcurrentLayerAccess: a layer open worker did not complete "
346 if ( expectValidLayer && worker->validOpens() != worker->loops() )
349 "testConcurrentLayerAccess: only %d of %d concurrent layer opens "
350 "gave a valid layer",
351 worker->validOpens(),
357 if ( sublayerWorker->consistentQueries() != sublayerWorker->loops() )
360 "testConcurrentLayerAccess: only %d of %d concurrent sublayer "
361 "queries returned the expected %d sublayers",
362 sublayerWorker->consistentQueries(),
363 sublayerWorker->loops(),
364 expectedSublayerCount
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
Base class for vector data providers.
virtual QgsAbstractFeatureSource * featureSource() const =0
Returns feature source object that can be used for querying provider's data.
Represents a vector layer which manages a vector based dataset.