1 /* 2 * Copyright (C) 2009, 2010, 2011 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 void TestLogFilteredData::marks() 358 { 359 logData_ = new LogData(); 360 361 // Register for notification file is loaded 362 connect( logData_, SIGNAL( loadingFinished( bool ) ), 363 this, SLOT( loadingFinished() ) ); 364 365 filteredData_ = logData_->getNewFilteredData(); 366 connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), 367 this, SLOT( searchProgressed( int, int ) ) ); 368 369 QFuture<void> future = QtConcurrent::run(this, &TestLogFilteredData::marksTest); 370 371 QApplication::exec(); 372 373 disconnect( filteredData_, 0 ); 374 disconnect( logData_, 0 ); 375 376 delete filteredData_; 377 delete logData_; 378 } 379 380 void TestLogFilteredData::marksTest() 381 { 382 // First load the tests file 383 logData_->attachFile( TMPDIR "/smalllog.txt" ); 384 // Wait for the loading to be done 385 waitLoadingFinished(); 386 QCOMPARE( logData_->getNbLine(), SL_NB_LINES ); 387 signalLoadingFinishedRead(); 388 389 // First check no line is marked 390 for ( int i = 0; i < SL_NB_LINES; i++ ) 391 QVERIFY( filteredData_->isLineMarked( i ) == false ); 392 393 // Try to create some "out of limit" marks 394 /* 395 filteredData_->addMark( -10 ); 396 filteredData_->addMark( SL_NB_LINES + 25 ); 397 398 // Check no line is marked still 399 for ( int i = 0; i < SL_NB_LINES; i++ ) 400 QVERIFY( filteredData_->isLineMarked( i ) == false ); 401 */ 402 403 // Create a couple of unnamed marks 404 filteredData_->addMark( 10 ); 405 filteredData_->addMark( 25 ); 406 407 // Check they are marked 408 QVERIFY( filteredData_->isLineMarked( 10 ) ); 409 QVERIFY( filteredData_->isLineMarked( 25 ) ); 410 411 // But others are not 412 QVERIFY( filteredData_->isLineMarked( 15 ) == false ); 413 QVERIFY( filteredData_->isLineMarked( 20 ) == false ); 414 415 QCOMPARE( filteredData_->getNbLine(), 2LL ); 416 417 // Performs a search 418 QSignalSpy progressSpy( filteredData_, 419 SIGNAL( searchProgressed( int, int ) ) ); 420 filteredData_->runSearch( QRegExp( "0000.4" ) ); 421 422 for ( int i = 0; i < 1; i++ ) { 423 waitSearchProgressed(); 424 signalSearchProgressedRead(); 425 } 426 427 // We should have the result of the search and the marks 428 waitSearchProgressed(); 429 QCOMPARE( filteredData_->getNbLine(), 12LL ); 430 signalSearchProgressedRead(); 431 432 QApplication::quit(); 433 } 434 435 // 436 // Private functions 437 // 438 void TestLogFilteredData::loadingFinished() 439 { 440 QMutexLocker locker( &loadingFinishedMutex_ ); 441 442 QWARN("loadingFinished"); 443 loadingFinished_received_ = true; 444 loadingFinished_read_ = false; 445 446 loadingFinishedCondition_.wakeOne(); 447 448 // Wait for the test thread to read the signal 449 while ( ! loadingFinished_read_ ) 450 loadingFinishedCondition_.wait( locker.mutex() ); 451 } 452 453 void TestLogFilteredData::searchProgressed( int nbMatches, int completion ) 454 { 455 QMutexLocker locker( &searchProgressedMutex_ ); 456 457 QWARN("searchProgressed"); 458 searchProgressed_received_ = true; 459 searchProgressed_read_ = false; 460 461 searchLastMatches_ = nbMatches; 462 searchLastProgress_ = completion; 463 464 searchProgressedCondition_.wakeOne(); 465 466 // Wait for the test thread to read the signal 467 while ( ! searchProgressed_read_ ) 468 searchProgressedCondition_.wait( locker.mutex() ); 469 } 470 471 std::pair<int,int> TestLogFilteredData::waitSearchProgressed() 472 { 473 QMutexLocker locker( &searchProgressedMutex_ ); 474 475 while ( ! searchProgressed_received_ ) 476 searchProgressedCondition_.wait( locker.mutex() ); 477 478 QWARN("searchProgressed Received"); 479 480 return std::pair<int,int>(searchLastMatches_, searchLastProgress_); 481 } 482 483 void TestLogFilteredData::waitLoadingFinished() 484 { 485 QMutexLocker locker( &loadingFinishedMutex_ ); 486 487 while ( ! loadingFinished_received_ ) 488 loadingFinishedCondition_.wait( locker.mutex() ); 489 490 QWARN("loadingFinished Received"); 491 } 492 493 void TestLogFilteredData::signalSearchProgressedRead() 494 { 495 QMutexLocker locker( &searchProgressedMutex_ ); 496 497 searchProgressed_received_ = false; 498 searchProgressed_read_ = true; 499 searchProgressedCondition_.wakeOne(); 500 } 501 502 void TestLogFilteredData::signalLoadingFinishedRead() 503 { 504 QMutexLocker locker( &loadingFinishedMutex_ ); 505 506 loadingFinished_received_ = false; 507 loadingFinished_read_ = true; 508 loadingFinishedCondition_.wakeOne(); 509 } 510 511 bool TestLogFilteredData::generateDataFiles() 512 { 513 char newLine[90]; 514 515 QFile file( TMPDIR "/mediumlog.txt" ); 516 if ( file.open( QIODevice::WriteOnly ) ) { 517 for (int i = 0; i < ML_NB_LINES; i++) { 518 snprintf(newLine, 89, ml_format, i); 519 file.write( newLine, qstrlen(newLine) ); 520 } 521 } 522 else { 523 return false; 524 } 525 file.close(); 526 527 file.setFileName( TMPDIR "/smalllog.txt" ); 528 if ( file.open( QIODevice::WriteOnly ) ) { 529 for (int i = 0; i < SL_NB_LINES; i++) { 530 snprintf(newLine, 89, sl_format, i); 531 file.write( newLine, qstrlen(newLine) ); 532 } 533 } 534 else { 535 return false; 536 } 537 file.close(); 538 539 return true; 540 } 541