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 filteredData_->addMark( -10 ); 395 filteredData_->addMark( SL_NB_LINES + 25 ); 396 397 // Check no line is marked still 398 for ( int i = 0; i < SL_NB_LINES; i++ ) 399 QVERIFY( filteredData_->isLineMarked( i ) == false ); 400 401 // Create a couple of unnamed marks 402 filteredData_->addMark( 10 ); 403 filteredData_->addMark( 44 ); // This one will also be a match 404 filteredData_->addMark( 25 ); 405 406 // Check they are marked 407 QVERIFY( filteredData_->isLineMarked( 10 ) ); 408 QVERIFY( filteredData_->isLineMarked( 25 ) ); 409 QVERIFY( filteredData_->isLineMarked( 44 ) ); 410 411 // But others are not 412 QVERIFY( filteredData_->isLineMarked( 15 ) == false ); 413 QVERIFY( filteredData_->isLineMarked( 20 ) == false ); 414 415 QCOMPARE( filteredData_->getNbLine(), 3LL ); 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 QString startline = "LOGDATA is a part of glogg, we are going to test it thoroughly, this is line "; 433 434 QCOMPARE( filteredData_->getLineString(0), startline + "000004" ); 435 QCOMPARE( filteredData_->getLineString(1), startline + "000010" ); 436 QCOMPARE( filteredData_->getLineString(2), startline + "000014" ); 437 QCOMPARE( filteredData_->getLineString(3), startline + "000024" ); 438 QCOMPARE( filteredData_->getLineString(4), startline + "000025" ); 439 QCOMPARE( filteredData_->getLineString(5), startline + "000034" ); 440 QCOMPARE( filteredData_->getLineString(6), startline + "000044" ); 441 QCOMPARE( filteredData_->getLineString(7), startline + "000054" ); 442 QCOMPARE( filteredData_->getLineString(8), startline + "000064" ); 443 QCOMPARE( filteredData_->getLineString(9), startline + "000074" ); 444 QCOMPARE( filteredData_->getLineString(10), startline + "000084" ); 445 QCOMPARE( filteredData_->getLineString(11), startline + "000094" ); 446 447 filteredData_->setVisibility( LogFilteredData::MatchesOnly ); 448 449 QCOMPARE( filteredData_->getNbLine(), 10LL ); 450 QCOMPARE( filteredData_->getLineString(0), startline + "000004" ); 451 QCOMPARE( filteredData_->getLineString(1), startline + "000014" ); 452 QCOMPARE( filteredData_->getLineString(2), startline + "000024" ); 453 QCOMPARE( filteredData_->getLineString(3), startline + "000034" ); 454 QCOMPARE( filteredData_->getLineString(4), startline + "000044" ); 455 QCOMPARE( filteredData_->getLineString(5), startline + "000054" ); 456 QCOMPARE( filteredData_->getLineString(6), startline + "000064" ); 457 QCOMPARE( filteredData_->getLineString(7), startline + "000074" ); 458 QCOMPARE( filteredData_->getLineString(8), startline + "000084" ); 459 QCOMPARE( filteredData_->getLineString(9), startline + "000094" ); 460 461 filteredData_->setVisibility( LogFilteredData::MarksOnly ); 462 463 QCOMPARE( filteredData_->getNbLine(), 3LL ); 464 QCOMPARE( filteredData_->getLineString(0), startline + "000010" ); 465 QCOMPARE( filteredData_->getLineString(1), startline + "000025" ); 466 QCOMPARE( filteredData_->getLineString(2), startline + "000044" ); 467 468 QApplication::quit(); 469 } 470 471 void TestLogFilteredData::lineLength() 472 { 473 logData_ = new LogData(); 474 475 // Register for notification file is loaded 476 connect( logData_, SIGNAL( loadingFinished( bool ) ), 477 this, SLOT( loadingFinished() ) ); 478 479 filteredData_ = logData_->getNewFilteredData(); 480 connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), 481 this, SLOT( searchProgressed( int, int ) ) ); 482 483 QFuture<void> future = QtConcurrent::run(this, &TestLogFilteredData::lineLengthTest); 484 485 QApplication::exec(); 486 487 disconnect( filteredData_, 0 ); 488 disconnect( logData_, 0 ); 489 490 delete filteredData_; 491 delete logData_; 492 } 493 494 void TestLogFilteredData::lineLengthTest() 495 { 496 // Line length tests 497 498 logData_->attachFile( TMPDIR "/length_test.txt" ); 499 // Wait for the loading to be done 500 waitLoadingFinished(); 501 QCOMPARE( logData_->getNbLine(), 4LL ); 502 signalLoadingFinishedRead(); 503 504 // Performs a search (the two middle lines matche) 505 filteredData_->setVisibility( LogFilteredData::MatchesOnly ); 506 filteredData_->runSearch( QRegExp( "longer" ) ); 507 508 std::pair<int,int> progress; 509 do { 510 progress = waitSearchProgressed(); 511 signalSearchProgressedRead(); 512 QWARN("progress"); 513 } while ( progress.second < 100 ); 514 515 filteredData_->addMark( 3 ); 516 517 QCOMPARE( filteredData_->getNbLine(), 2LL ); 518 QCOMPARE( filteredData_->getMaxLength(), 40 ); 519 520 filteredData_->setVisibility( LogFilteredData::MarksAndMatches ); 521 QCOMPARE( filteredData_->getNbLine(), 3LL ); 522 QCOMPARE( filteredData_->getMaxLength(), 103 ); 523 524 filteredData_->setVisibility( LogFilteredData::MarksOnly ); 525 QCOMPARE( filteredData_->getNbLine(), 1LL ); 526 QCOMPARE( filteredData_->getMaxLength(), 103 ); 527 528 filteredData_->addMark( 0 ); 529 QCOMPARE( filteredData_->getNbLine(), 2LL ); 530 QCOMPARE( filteredData_->getMaxLength(), 103 ); 531 filteredData_->deleteMark( 3 ); 532 QCOMPARE( filteredData_->getNbLine(), 1LL ); 533 QCOMPARE( filteredData_->getMaxLength(), 27 ); 534 535 filteredData_->setVisibility( LogFilteredData::MarksAndMatches ); 536 QCOMPARE( filteredData_->getMaxLength(), 40 ); 537 538 QApplication::quit(); 539 } 540 541 // 542 // Private functions 543 // 544 void TestLogFilteredData::loadingFinished() 545 { 546 QMutexLocker locker( &loadingFinishedMutex_ ); 547 548 QWARN("loadingFinished"); 549 loadingFinished_received_ = true; 550 loadingFinished_read_ = false; 551 552 loadingFinishedCondition_.wakeOne(); 553 554 // Wait for the test thread to read the signal 555 while ( ! loadingFinished_read_ ) 556 loadingFinishedCondition_.wait( locker.mutex() ); 557 } 558 559 void TestLogFilteredData::searchProgressed( int nbMatches, int completion ) 560 { 561 QMutexLocker locker( &searchProgressedMutex_ ); 562 563 QWARN("searchProgressed"); 564 searchProgressed_received_ = true; 565 searchProgressed_read_ = false; 566 567 searchLastMatches_ = nbMatches; 568 searchLastProgress_ = completion; 569 570 searchProgressedCondition_.wakeOne(); 571 572 // Wait for the test thread to read the signal 573 while ( ! searchProgressed_read_ ) 574 searchProgressedCondition_.wait( locker.mutex() ); 575 } 576 577 std::pair<int,int> TestLogFilteredData::waitSearchProgressed() 578 { 579 QMutexLocker locker( &searchProgressedMutex_ ); 580 581 while ( ! searchProgressed_received_ ) 582 searchProgressedCondition_.wait( locker.mutex() ); 583 584 QWARN("searchProgressed Received"); 585 586 return std::pair<int,int>(searchLastMatches_, searchLastProgress_); 587 } 588 589 void TestLogFilteredData::waitLoadingFinished() 590 { 591 QMutexLocker locker( &loadingFinishedMutex_ ); 592 593 while ( ! loadingFinished_received_ ) 594 loadingFinishedCondition_.wait( locker.mutex() ); 595 596 QWARN("loadingFinished Received"); 597 } 598 599 void TestLogFilteredData::signalSearchProgressedRead() 600 { 601 QMutexLocker locker( &searchProgressedMutex_ ); 602 603 searchProgressed_received_ = false; 604 searchProgressed_read_ = true; 605 searchProgressedCondition_.wakeOne(); 606 } 607 608 void TestLogFilteredData::signalLoadingFinishedRead() 609 { 610 QMutexLocker locker( &loadingFinishedMutex_ ); 611 612 loadingFinished_received_ = false; 613 loadingFinished_read_ = true; 614 loadingFinishedCondition_.wakeOne(); 615 } 616 617 bool TestLogFilteredData::generateDataFiles() 618 { 619 char newLine[90]; 620 621 QFile file( TMPDIR "/mediumlog.txt" ); 622 if ( file.open( QIODevice::WriteOnly ) ) { 623 for (int i = 0; i < ML_NB_LINES; i++) { 624 snprintf(newLine, 89, ml_format, i); 625 file.write( newLine, qstrlen(newLine) ); 626 } 627 } 628 else { 629 return false; 630 } 631 file.close(); 632 633 file.setFileName( TMPDIR "/smalllog.txt" ); 634 if ( file.open( QIODevice::WriteOnly ) ) { 635 for (int i = 0; i < SL_NB_LINES; i++) { 636 snprintf(newLine, 89, sl_format, i); 637 file.write( newLine, qstrlen(newLine) ); 638 } 639 } 640 else { 641 return false; 642 } 643 file.close(); 644 645 file.setFileName( TMPDIR "/length_test.txt" ); 646 if ( file.open( QIODevice::WriteOnly ) ) { 647 file.write( "This line is 28 characters.\n" ); 648 file.write( "This line is longer: 36 characters.\n" ); 649 file.write( "This line is even longer: 41 characters.\n" ); 650 file.write( "This line is very long, it's actually hard to count but it is\ 651 probably something around 102 characters.\n" ); 652 } 653 file.close(); 654 655 return true; 656 } 657