1 /* 2 * Copyright (C) 2009, 2010 Nicolas Bonnefon and other contributors 3 * 4 * This file is part of glogg. 5 * 6 * glogg is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * glogg is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with glogg. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include <QSignalSpy> 21 #include <QMutexLocker> 22 #include <QFile> 23 24 #include "testlogfiltereddata.h" 25 #include "logdata.h" 26 #include "logfiltereddata.h" 27 28 #if QT_VERSION < 0x040500 29 #define QBENCHMARK 30 #endif 31 32 #if !defined( TMPDIR ) 33 #define TMPDIR "/tmp" 34 #endif 35 36 static const qint64 ML_NB_LINES = 15000LL; 37 static const char* ml_format="LOGDATA is a part of glogg, we are going to test it thoroughly, this is line\t\t%06d\n"; 38 static const int ML_VISIBLE_LINE_LENGTH = (76+8+4+6); // Without the final '\n' ! 39 40 static const qint64 SL_NB_LINES = 2000LL; 41 static const char* sl_format="LOGDATA is a part of glogg, we are going to test it thoroughly, this is line %06d\n"; 42 43 static const char* partial_line_begin = "123... beginning of line."; 44 static const char* partial_line_end = " end of line 123.\n"; 45 46 static const char* partial_nonmatching_line_begin = "Beginning of line."; 47 48 TestLogFilteredData::TestLogFilteredData() : QObject(), 49 loadingFinishedMutex_(), 50 searchProgressedMutex_(), 51 loadingFinishedCondition_(), 52 searchProgressedCondition_() 53 { 54 loadingFinished_received_ = false; 55 loadingFinished_read_ = false; 56 searchProgressed_received_ = false; 57 searchProgressed_read_ = false; 58 } 59 60 void TestLogFilteredData::initTestCase() 61 { 62 QVERIFY( generateDataFiles() ); 63 } 64 65 void TestLogFilteredData::simpleSearch() 66 { 67 logData_ = new LogData(); 68 69 // Register for notification file is loaded 70 connect( logData_, SIGNAL( loadingFinished( bool ) ), 71 this, SLOT( loadingFinished() ) ); 72 73 filteredData_ = logData_->getNewFilteredData(); 74 connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), 75 this, SLOT( searchProgressed( int, int ) ) ); 76 77 QFuture<void> future = QtConcurrent::run(this, &TestLogFilteredData::simpleSearchTest); 78 79 QApplication::exec(); 80 81 disconnect( filteredData_, 0 ); 82 disconnect( logData_, 0 ); 83 84 delete filteredData_; 85 delete logData_; 86 } 87 88 void TestLogFilteredData::simpleSearchTest() 89 { 90 // First load the tests file 91 logData_->attachFile( TMPDIR "/mediumlog.txt" ); 92 // Wait for the loading to be done 93 waitLoadingFinished(); 94 QCOMPARE( logData_->getNbLine(), ML_NB_LINES ); 95 signalLoadingFinishedRead(); 96 97 // Now perform a simple search 98 qint64 matches[] = { 0, 15, 20, 135 }; 99 QBENCHMARK { 100 // Start the search 101 filteredData_->runSearch( QRegExp( "123" ) ); 102 103 // And check we receive data in 4 chunks (the first being empty) 104 for ( int i = 0; i < 4; i++ ) { 105 std::pair<int,int> progress = waitSearchProgressed(); 106 QCOMPARE( (qint64) progress.first, matches[i] ); 107 signalSearchProgressedRead(); 108 } 109 } 110 111 QCOMPARE( filteredData_->getNbLine(), matches[3] ); 112 // Check the search 113 QCOMPARE( filteredData_->isLineInMatchingList( 123 ), true ); 114 QCOMPARE( filteredData_->isLineInMatchingList( 124 ), false ); 115 QCOMPARE( filteredData_->getMaxLength(), ML_VISIBLE_LINE_LENGTH ); 116 QCOMPARE( filteredData_->getLineLength( 12 ), ML_VISIBLE_LINE_LENGTH ); 117 QCOMPARE( filteredData_->getNbLine(), 135LL ); 118 // Line beyond limit 119 QCOMPARE( filteredData_->isLineInMatchingList( 60000 ), false ); 120 QCOMPARE( filteredData_->getMatchingLineNumber( 0 ), 123LL ); 121 122 // Now let's try interrupting a search 123 filteredData_->runSearch( QRegExp( "123" ) ); 124 // ... wait for two chunks. 125 waitSearchProgressed(); 126 signalSearchProgressedRead(); 127 waitSearchProgressed(); 128 // and interrupt! 129 filteredData_->interruptSearch(); 130 QCOMPARE( filteredData_->getNbLine(), matches[1] ); 131 waitSearchProgressed(); 132 // After interrupt: should be 100% and the same number of matches 133 QCOMPARE( filteredData_->getNbLine(), matches[1] ); 134 signalSearchProgressedRead(); 135 136 // (because there is no guarantee when the search is 137 // interrupted, we are not sure how many chunk of result 138 // we will get.) 139 140 QApplication::quit(); 141 } 142 143 void TestLogFilteredData::multipleSearch() 144 { 145 logData_ = new LogData(); 146 147 // Register for notification file is loaded 148 connect( logData_, SIGNAL( loadingFinished( bool ) ), 149 this, SLOT( loadingFinished() ) ); 150 151 filteredData_ = logData_->getNewFilteredData(); 152 connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), 153 this, SLOT( searchProgressed( int, int ) ) ); 154 155 QFuture<void> future = QtConcurrent::run(this, &TestLogFilteredData::multipleSearchTest); 156 157 QApplication::exec(); 158 159 disconnect( filteredData_, 0 ); 160 disconnect( logData_, 0 ); 161 162 delete filteredData_; 163 delete logData_; 164 } 165 166 void TestLogFilteredData::multipleSearchTest() 167 { 168 // First load the tests file 169 logData_->attachFile( TMPDIR "/smalllog.txt" ); 170 // Wait for the loading to be done 171 waitLoadingFinished(); 172 QCOMPARE( logData_->getNbLine(), SL_NB_LINES ); 173 signalLoadingFinishedRead(); 174 175 // Performs two searches in a row 176 QSignalSpy progressSpy( filteredData_, 177 SIGNAL( searchProgressed( int, int ) ) ); 178 179 // Start the search, and immediately another one 180 // (the second call should block until the first search is done) 181 filteredData_->runSearch( QRegExp( "1234" ) ); 182 filteredData_->runSearch( QRegExp( "123" ) ); 183 184 for ( int i = 0; i < 3; i++ ) { 185 waitSearchProgressed(); 186 signalSearchProgressedRead(); 187 } 188 189 // We should have the result for the 2nd search after the last chunk 190 waitSearchProgressed(); 191 QCOMPARE( filteredData_->getNbLine(), 12LL ); 192 signalSearchProgressedRead(); 193 194 // Now a tricky one: we run a search and immediately attach a new file 195 filteredData_->runSearch( QRegExp( "123" ) ); 196 waitSearchProgressed(); 197 signalSearchProgressedRead(); 198 logData_->attachFile( TMPDIR "/mediumlog.txt" ); 199 200 // We don't expect meaningful results but it should not crash! 201 for ( int i = 0; i < 1; i++ ) { 202 waitSearchProgressed(); 203 signalSearchProgressedRead(); 204 } 205 206 QApplication::quit(); 207 } 208 209 void TestLogFilteredData::updateSearch() 210 { 211 logData_ = new LogData(); 212 213 // Register for notification file is loaded 214 connect( logData_, SIGNAL( loadingFinished( bool ) ), 215 this, SLOT( loadingFinished() ) ); 216 217 filteredData_ = logData_->getNewFilteredData(); 218 connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), 219 this, SLOT( searchProgressed( int, int ) ) ); 220 221 QFuture<void> future = QtConcurrent::run(this, &TestLogFilteredData::updateSearchTest); 222 223 QApplication::exec(); 224 225 disconnect( filteredData_, 0 ); 226 disconnect( logData_, 0 ); 227 228 delete filteredData_; 229 delete logData_; 230 } 231 232 void TestLogFilteredData::updateSearchTest() 233 { 234 // First load the tests file 235 logData_->attachFile( TMPDIR "/smalllog.txt" ); 236 // Wait for the loading to be done 237 waitLoadingFinished(); 238 QCOMPARE( logData_->getNbLine(), SL_NB_LINES ); 239 signalLoadingFinishedRead(); 240 241 // Perform a first search 242 filteredData_->runSearch( QRegExp( "123" ) ); 243 244 for ( int i = 0; i < 2; i++ ) { 245 waitSearchProgressed(); 246 signalSearchProgressedRead(); 247 } 248 249 // Check the result 250 QCOMPARE( filteredData_->getNbLine(), 12LL ); 251 252 QWARN("Starting stage 2"); 253 254 // Add some data to the file 255 char newLine[90]; 256 QFile file( TMPDIR "/smalllog.txt" ); 257 if ( file.open( QIODevice::Append ) ) { 258 for (int i = 0; i < 3000; i++) { 259 snprintf(newLine, 89, sl_format, i); 260 file.write( newLine, qstrlen(newLine) ); 261 } 262 // To test the edge case when the final line is not complete and matching 263 file.write( partial_line_begin, qstrlen( partial_line_begin ) ); 264 } 265 file.close(); 266 267 // Let the system do the update 268 waitLoadingFinished(); 269 signalLoadingFinishedRead(); 270 271 // Start an update search 272 filteredData_->updateSearch(); 273 274 for ( int i = 0; i < 2; i++ ) { 275 waitSearchProgressed(); 276 signalSearchProgressedRead(); 277 } 278 279 // Check the result 280 QCOMPARE( logData_->getNbLine(), 5001LL ); 281 QCOMPARE( filteredData_->getNbLine(), 26LL ); 282 283 QWARN("Starting stage 3"); 284 285 // Add a couple more lines, including the end of the unfinished one. 286 if ( file.open( QIODevice::Append ) ) { 287 file.write( partial_line_end, qstrlen( partial_line_end ) ); 288 for (int i = 0; i < 20; i++) { 289 snprintf(newLine, 89, sl_format, i); 290 file.write( newLine, qstrlen(newLine) ); 291 } 292 // To test the edge case when the final line is not complete and not matching 293 file.write( partial_nonmatching_line_begin, 294 qstrlen( partial_nonmatching_line_begin ) ); 295 } 296 file.close(); 297 298 // Let the system do the update 299 waitLoadingFinished(); 300 signalLoadingFinishedRead(); 301 302 // Start an update search 303 filteredData_->updateSearch(); 304 305 for ( int i = 0; i < 2; i++ ) { 306 waitSearchProgressed(); 307 signalSearchProgressedRead(); 308 } 309 310 // Check the result 311 QCOMPARE( logData_->getNbLine(), 5022LL ); 312 QCOMPARE( filteredData_->getNbLine(), 26LL ); 313 314 QWARN("Starting stage 4"); 315 316 // Now test the case where a match is found at the end of an updated line. 317 if ( file.open( QIODevice::Append ) ) { 318 file.write( partial_line_end, qstrlen( partial_line_end ) ); 319 for (int i = 0; i < 20; i++) { 320 snprintf(newLine, 89, sl_format, i); 321 file.write( newLine, qstrlen(newLine) ); 322 } 323 } 324 file.close(); 325 326 // Let the system do the update 327 waitLoadingFinished(); 328 signalLoadingFinishedRead(); 329 330 // Start an update search 331 filteredData_->updateSearch(); 332 333 for ( int i = 0; i < 2; i++ ) { 334 waitSearchProgressed(); 335 signalSearchProgressedRead(); 336 } 337 338 // Check the result 339 QCOMPARE( logData_->getNbLine(), 5042LL ); 340 QCOMPARE( filteredData_->getNbLine(), 27LL ); 341 342 QApplication::quit(); 343 } 344 345 // 346 // Private functions 347 // 348 void TestLogFilteredData::loadingFinished() 349 { 350 QMutexLocker locker( &loadingFinishedMutex_ ); 351 352 QWARN("loadingFinished"); 353 loadingFinished_received_ = true; 354 loadingFinished_read_ = false; 355 356 loadingFinishedCondition_.wakeOne(); 357 358 // Wait for the test thread to read the signal 359 while ( ! loadingFinished_read_ ) 360 loadingFinishedCondition_.wait( locker.mutex() ); 361 } 362 363 void TestLogFilteredData::searchProgressed( int nbMatches, int completion ) 364 { 365 QMutexLocker locker( &searchProgressedMutex_ ); 366 367 QWARN("searchProgressed"); 368 searchProgressed_received_ = true; 369 searchProgressed_read_ = false; 370 371 searchLastMatches_ = nbMatches; 372 searchLastProgress_ = completion; 373 374 searchProgressedCondition_.wakeOne(); 375 376 // Wait for the test thread to read the signal 377 while ( ! searchProgressed_read_ ) 378 searchProgressedCondition_.wait( locker.mutex() ); 379 } 380 381 std::pair<int,int> TestLogFilteredData::waitSearchProgressed() 382 { 383 QMutexLocker locker( &searchProgressedMutex_ ); 384 385 while ( ! searchProgressed_received_ ) 386 searchProgressedCondition_.wait( locker.mutex() ); 387 388 QWARN("searchProgressed Received"); 389 390 return std::pair<int,int>(searchLastMatches_, searchLastProgress_); 391 } 392 393 void TestLogFilteredData::waitLoadingFinished() 394 { 395 QMutexLocker locker( &loadingFinishedMutex_ ); 396 397 while ( ! loadingFinished_received_ ) 398 loadingFinishedCondition_.wait( locker.mutex() ); 399 400 QWARN("loadingFinished Received"); 401 } 402 403 void TestLogFilteredData::signalSearchProgressedRead() 404 { 405 QMutexLocker locker( &searchProgressedMutex_ ); 406 407 searchProgressed_received_ = false; 408 searchProgressed_read_ = true; 409 searchProgressedCondition_.wakeOne(); 410 } 411 412 void TestLogFilteredData::signalLoadingFinishedRead() 413 { 414 QMutexLocker locker( &loadingFinishedMutex_ ); 415 416 loadingFinished_received_ = false; 417 loadingFinished_read_ = true; 418 loadingFinishedCondition_.wakeOne(); 419 } 420 421 bool TestLogFilteredData::generateDataFiles() 422 { 423 char newLine[90]; 424 425 QFile file( TMPDIR "/mediumlog.txt" ); 426 if ( file.open( QIODevice::WriteOnly ) ) { 427 for (int i = 0; i < ML_NB_LINES; i++) { 428 snprintf(newLine, 89, ml_format, i); 429 file.write( newLine, qstrlen(newLine) ); 430 } 431 } 432 else { 433 return false; 434 } 435 file.close(); 436 437 file.setFileName( TMPDIR "/smalllog.txt" ); 438 if ( file.open( QIODevice::WriteOnly ) ) { 439 for (int i = 0; i < SL_NB_LINES; i++) { 440 snprintf(newLine, 89, sl_format, i); 441 file.write( newLine, qstrlen(newLine) ); 442 } 443 } 444 else { 445 return false; 446 } 447 file.close(); 448 449 return true; 450 } 451