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 // FIXME: The test for this is unfortunately not reliable 107 // (race conditions) 108 // QCOMPARE( (qint64) progress.first, matches[i] ); 109 signalSearchProgressedRead(); 110 } 111 } 112 113 QCOMPARE( filteredData_->getNbLine(), matches[3] ); 114 // Check the search 115 QCOMPARE( filteredData_->isLineInMatchingList( 123 ), true ); 116 QCOMPARE( filteredData_->isLineInMatchingList( 124 ), false ); 117 QCOMPARE( filteredData_->getMaxLength(), ML_VISIBLE_LINE_LENGTH ); 118 QCOMPARE( filteredData_->getLineLength( 12 ), ML_VISIBLE_LINE_LENGTH ); 119 QCOMPARE( filteredData_->getNbLine(), 135LL ); 120 // Line beyond limit 121 QCOMPARE( filteredData_->isLineInMatchingList( 60000 ), false ); 122 QCOMPARE( filteredData_->getMatchingLineNumber( 0 ), 123LL ); 123 124 // Now let's try interrupting a search 125 filteredData_->runSearch( QRegExp( "123" ) ); 126 // ... wait for two chunks. 127 waitSearchProgressed(); 128 signalSearchProgressedRead(); 129 // and interrupt! 130 filteredData_->interruptSearch(); 131 132 { 133 std::pair<int,int> progress; 134 do { 135 progress = waitSearchProgressed(); 136 signalSearchProgressedRead(); 137 } while ( progress.second < 100 ); 138 139 // (because there is no guarantee when the search is 140 // interrupted, we are not sure how many chunk of result 141 // we will get.) 142 } 143 144 QApplication::quit(); 145 } 146 147 void TestLogFilteredData::multipleSearch() 148 { 149 logData_ = new LogData(); 150 151 // Register for notification file is loaded 152 connect( logData_, SIGNAL( loadingFinished( bool ) ), 153 this, SLOT( loadingFinished() ) ); 154 155 filteredData_ = logData_->getNewFilteredData(); 156 connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), 157 this, SLOT( searchProgressed( int, int ) ) ); 158 159 QFuture<void> future = QtConcurrent::run(this, &TestLogFilteredData::multipleSearchTest); 160 161 QApplication::exec(); 162 163 disconnect( filteredData_, 0 ); 164 disconnect( logData_, 0 ); 165 166 delete filteredData_; 167 delete logData_; 168 } 169 170 void TestLogFilteredData::multipleSearchTest() 171 { 172 // First load the tests file 173 logData_->attachFile( TMPDIR "/smalllog.txt" ); 174 // Wait for the loading to be done 175 waitLoadingFinished(); 176 QCOMPARE( logData_->getNbLine(), SL_NB_LINES ); 177 signalLoadingFinishedRead(); 178 179 // Performs two searches in a row 180 // Start the search, and immediately another one 181 // (the second call should block until the first search is done) 182 filteredData_->runSearch( QRegExp( "1234" ) ); 183 filteredData_->runSearch( QRegExp( "123" ) ); 184 185 for ( int i = 0; i < 3; i++ ) { 186 waitSearchProgressed(); 187 signalSearchProgressedRead(); 188 } 189 190 // We should have the result for the 2nd search after the last chunk 191 waitSearchProgressed(); 192 QCOMPARE( filteredData_->getNbLine(), 12LL ); 193 signalSearchProgressedRead(); 194 195 // Now a tricky one: we run a search and immediately attach a new file 196 /* FIXME: sometimes we receive loadingFinished before searchProgressed 197 * -> deadlock in the test. 198 filteredData_->runSearch( QRegExp( "123" ) ); 199 waitSearchProgressed(); 200 signalSearchProgressedRead(); 201 logData_->attachFile( TMPDIR "/mediumlog.txt" ); 202 203 // We don't expect meaningful results but it should not crash! 204 for ( int i = 0; i < 1; i++ ) { 205 waitSearchProgressed(); 206 signalSearchProgressedRead(); 207 } 208 */ 209 210 sleep(10); 211 212 QApplication::quit(); 213 } 214 215 void TestLogFilteredData::updateSearch() 216 { 217 logData_ = new LogData(); 218 219 // Register for notification file is loaded 220 connect( logData_, SIGNAL( loadingFinished( bool ) ), 221 this, SLOT( loadingFinished() ) ); 222 223 filteredData_ = logData_->getNewFilteredData(); 224 connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), 225 this, SLOT( searchProgressed( int, int ) ) ); 226 227 QFuture<void> future = QtConcurrent::run(this, &TestLogFilteredData::updateSearchTest); 228 229 QApplication::exec(); 230 231 disconnect( filteredData_, 0 ); 232 disconnect( logData_, 0 ); 233 234 delete filteredData_; 235 delete logData_; 236 } 237 238 void TestLogFilteredData::updateSearchTest() 239 { 240 // First load the tests file 241 logData_->attachFile( TMPDIR "/smalllog.txt" ); 242 // Wait for the loading to be done 243 waitLoadingFinished(); 244 QCOMPARE( logData_->getNbLine(), SL_NB_LINES ); 245 signalLoadingFinishedRead(); 246 247 // Perform a first search 248 filteredData_->runSearch( QRegExp( "123" ) ); 249 250 for ( int i = 0; i < 2; i++ ) { 251 waitSearchProgressed(); 252 signalSearchProgressedRead(); 253 } 254 255 // Check the result 256 QCOMPARE( filteredData_->getNbLine(), 12LL ); 257 258 sleep(1); 259 260 QWARN("Starting stage 2"); 261 262 // Add some data to the file 263 char newLine[90]; 264 QFile file( TMPDIR "/smalllog.txt" ); 265 if ( file.open( QIODevice::Append ) ) { 266 for (int i = 0; i < 3000; i++) { 267 snprintf(newLine, 89, sl_format, i); 268 file.write( newLine, qstrlen(newLine) ); 269 } 270 // To test the edge case when the final line is not complete and matching 271 file.write( partial_line_begin, qstrlen( partial_line_begin ) ); 272 } 273 file.close(); 274 275 // Let the system do the update (there might be several ones) 276 do { 277 waitLoadingFinished(); 278 signalLoadingFinishedRead(); 279 } while ( logData_->getNbLine() < 5001LL ); 280 281 sleep(1); 282 283 // Start an update search 284 filteredData_->updateSearch(); 285 286 for ( int i = 0; i < 2; i++ ) { 287 waitSearchProgressed(); 288 signalSearchProgressedRead(); 289 } 290 291 // Check the result 292 QCOMPARE( logData_->getNbLine(), 5001LL ); 293 QCOMPARE( filteredData_->getNbLine(), 26LL ); 294 295 QWARN("Starting stage 3"); 296 297 // Add a couple more lines, including the end of the unfinished one. 298 if ( file.open( QIODevice::Append ) ) { 299 file.write( partial_line_end, qstrlen( partial_line_end ) ); 300 for (int i = 0; i < 20; i++) { 301 snprintf(newLine, 89, sl_format, i); 302 file.write( newLine, qstrlen(newLine) ); 303 } 304 // To test the edge case when the final line is not complete and not matching 305 file.write( partial_nonmatching_line_begin, 306 qstrlen( partial_nonmatching_line_begin ) ); 307 } 308 file.close(); 309 310 // Let the system do the update 311 waitLoadingFinished(); 312 signalLoadingFinishedRead(); 313 314 // Start an update search 315 filteredData_->updateSearch(); 316 317 for ( int i = 0; i < 2; i++ ) { 318 waitSearchProgressed(); 319 signalSearchProgressedRead(); 320 } 321 322 // Check the result 323 QCOMPARE( logData_->getNbLine(), 5022LL ); 324 QCOMPARE( filteredData_->getNbLine(), 26LL ); 325 326 QWARN("Starting stage 4"); 327 328 // Now test the case where a match is found at the end of an updated line. 329 if ( file.open( QIODevice::Append ) ) { 330 file.write( partial_line_end, qstrlen( partial_line_end ) ); 331 for (int i = 0; i < 20; i++) { 332 snprintf(newLine, 89, sl_format, i); 333 file.write( newLine, qstrlen(newLine) ); 334 } 335 } 336 file.close(); 337 338 // Let the system do the update 339 waitLoadingFinished(); 340 signalLoadingFinishedRead(); 341 342 // Start an update search 343 filteredData_->updateSearch(); 344 345 for ( int i = 0; i < 2; i++ ) { 346 waitSearchProgressed(); 347 signalSearchProgressedRead(); 348 } 349 350 // Check the result 351 QCOMPARE( logData_->getNbLine(), 5042LL ); 352 QCOMPARE( filteredData_->getNbLine(), 27LL ); 353 354 QApplication::quit(); 355 } 356 357 // 358 // Private functions 359 // 360 void TestLogFilteredData::loadingFinished() 361 { 362 QMutexLocker locker( &loadingFinishedMutex_ ); 363 364 QWARN("loadingFinished"); 365 loadingFinished_received_ = true; 366 loadingFinished_read_ = false; 367 368 loadingFinishedCondition_.wakeOne(); 369 370 // Wait for the test thread to read the signal 371 while ( ! loadingFinished_read_ ) 372 loadingFinishedCondition_.wait( locker.mutex() ); 373 } 374 375 void TestLogFilteredData::searchProgressed( int nbMatches, int completion ) 376 { 377 QMutexLocker locker( &searchProgressedMutex_ ); 378 379 QWARN("searchProgressed"); 380 searchProgressed_received_ = true; 381 searchProgressed_read_ = false; 382 383 searchLastMatches_ = nbMatches; 384 searchLastProgress_ = completion; 385 386 searchProgressedCondition_.wakeOne(); 387 388 // Wait for the test thread to read the signal 389 while ( ! searchProgressed_read_ ) 390 searchProgressedCondition_.wait( locker.mutex() ); 391 } 392 393 std::pair<int,int> TestLogFilteredData::waitSearchProgressed() 394 { 395 QMutexLocker locker( &searchProgressedMutex_ ); 396 397 while ( ! searchProgressed_received_ ) 398 searchProgressedCondition_.wait( locker.mutex() ); 399 400 QWARN("searchProgressed Received"); 401 402 return std::pair<int,int>(searchLastMatches_, searchLastProgress_); 403 } 404 405 void TestLogFilteredData::waitLoadingFinished() 406 { 407 QMutexLocker locker( &loadingFinishedMutex_ ); 408 409 while ( ! loadingFinished_received_ ) 410 loadingFinishedCondition_.wait( locker.mutex() ); 411 412 QWARN("loadingFinished Received"); 413 } 414 415 void TestLogFilteredData::signalSearchProgressedRead() 416 { 417 QMutexLocker locker( &searchProgressedMutex_ ); 418 419 searchProgressed_received_ = false; 420 searchProgressed_read_ = true; 421 searchProgressedCondition_.wakeOne(); 422 } 423 424 void TestLogFilteredData::signalLoadingFinishedRead() 425 { 426 QMutexLocker locker( &loadingFinishedMutex_ ); 427 428 loadingFinished_received_ = false; 429 loadingFinished_read_ = true; 430 loadingFinishedCondition_.wakeOne(); 431 } 432 433 bool TestLogFilteredData::generateDataFiles() 434 { 435 char newLine[90]; 436 437 QFile file( TMPDIR "/mediumlog.txt" ); 438 if ( file.open( QIODevice::WriteOnly ) ) { 439 for (int i = 0; i < ML_NB_LINES; i++) { 440 snprintf(newLine, 89, ml_format, i); 441 file.write( newLine, qstrlen(newLine) ); 442 } 443 } 444 else { 445 return false; 446 } 447 file.close(); 448 449 file.setFileName( TMPDIR "/smalllog.txt" ); 450 if ( file.open( QIODevice::WriteOnly ) ) { 451 for (int i = 0; i < SL_NB_LINES; i++) { 452 snprintf(newLine, 89, sl_format, i); 453 file.write( newLine, qstrlen(newLine) ); 454 } 455 } 456 else { 457 return false; 458 } 459 file.close(); 460 461 return true; 462 } 463