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 !defined( TMPDIR )
29 #define TMPDIR "/tmp"
30 #endif
31
32 static const qint64 ML_NB_LINES = 15000LL;
33 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";
34 static const int ML_VISIBLE_LINE_LENGTH = (76+8+4+6); // Without the final '\n' !
35
36 static const qint64 SL_NB_LINES = 2000LL;
37 static const char* sl_format="LOGDATA is a part of glogg, we are going to test it thoroughly, this is line %06d\n";
38
39 static const char* partial_line_begin = "123... beginning of line.";
40 static const char* partial_line_end = " end of line 123.\n";
41
42 static const char* partial_nonmatching_line_begin = "Beginning of line.";
43
TestLogFilteredData()44 TestLogFilteredData::TestLogFilteredData() : QObject(),
45 loadingFinishedMutex_(),
46 searchProgressedMutex_(),
47 loadingFinishedCondition_(),
48 searchProgressedCondition_()
49 {
50 loadingFinished_received_ = false;
51 loadingFinished_read_ = false;
52 searchProgressed_received_ = false;
53 searchProgressed_read_ = false;
54 }
55
initTestCase()56 void TestLogFilteredData::initTestCase()
57 {
58 QVERIFY( generateDataFiles() );
59 }
60
simpleSearch()61 void TestLogFilteredData::simpleSearch()
62 {
63 logData_ = new LogData();
64
65 // Register for notification file is loaded
66 connect( logData_, SIGNAL( loadingFinished( bool ) ),
67 this, SLOT( loadingFinished() ) );
68
69 filteredData_ = logData_->getNewFilteredData();
70 connect( filteredData_, SIGNAL( searchProgressed( int, int ) ),
71 this, SLOT( searchProgressed( int, int ) ) );
72
73 QFuture<void> future = QtConcurrent::run(this, &TestLogFilteredData::simpleSearchTest);
74
75 QApplication::exec();
76
77 disconnect( filteredData_, 0 );
78 disconnect( logData_, 0 );
79
80 delete filteredData_;
81 delete logData_;
82 }
83
simpleSearchTest()84 void TestLogFilteredData::simpleSearchTest()
85 {
86 // First load the tests file
87 logData_->attachFile( TMPDIR "/mediumlog.txt" );
88 // Wait for the loading to be done
89 waitLoadingFinished();
90 QCOMPARE( logData_->getNbLine(), ML_NB_LINES );
91 signalLoadingFinishedRead();
92
93 // Now perform a simple search
94 qint64 matches[] = { 0, 15, 20, 135 };
95 QBENCHMARK {
96 // Start the search
97 filteredData_->runSearch( QRegularExpression( "123" ) );
98
99 // And check we receive data in 4 chunks (the first being empty)
100 for ( int i = 0; i < 4; i++ ) {
101 std::pair<int,int> progress = waitSearchProgressed();
102 // FIXME: The test for this is unfortunately not reliable
103 // (race conditions)
104 // QCOMPARE( (qint64) progress.first, matches[i] );
105 signalSearchProgressedRead();
106 }
107 }
108
109 QCOMPARE( filteredData_->getNbLine(), matches[3] );
110 // Check the search
111 QCOMPARE( filteredData_->isLineInMatchingList( 123 ), true );
112 QCOMPARE( filteredData_->isLineInMatchingList( 124 ), false );
113 QCOMPARE( filteredData_->getMaxLength(), ML_VISIBLE_LINE_LENGTH );
114 QCOMPARE( filteredData_->getLineLength( 12 ), ML_VISIBLE_LINE_LENGTH );
115 QCOMPARE( filteredData_->getNbLine(), 135LL );
116 // Line beyond limit
117 QCOMPARE( filteredData_->isLineInMatchingList( 60000 ), false );
118 QCOMPARE( filteredData_->getMatchingLineNumber( 0 ), 123LL );
119
120 // Now let's try interrupting a search
121 filteredData_->runSearch( QRegularExpression( "123" ) );
122 // ... wait for two chunks.
123 waitSearchProgressed();
124 signalSearchProgressedRead();
125 // and interrupt!
126 filteredData_->interruptSearch();
127
128 {
129 std::pair<int,int> progress;
130 do {
131 progress = waitSearchProgressed();
132 signalSearchProgressedRead();
133 } while ( progress.second < 100 );
134
135 // (because there is no guarantee when the search is
136 // interrupted, we are not sure how many chunk of result
137 // we will get.)
138 }
139
140 QApplication::quit();
141 }
142
multipleSearch()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
multipleSearchTest()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 // Start the search, and immediately another one
177 // (the second call should block until the first search is done)
178 filteredData_->runSearch( QRegularExpression( "1234" ) );
179 filteredData_->runSearch( QRegularExpression( "123" ) );
180
181 for ( int i = 0; i < 3; i++ ) {
182 waitSearchProgressed();
183 signalSearchProgressedRead();
184 }
185
186 // We should have the result for the 2nd search after the last chunk
187 waitSearchProgressed();
188 QCOMPARE( filteredData_->getNbLine(), 12LL );
189 signalSearchProgressedRead();
190
191 // Now a tricky one: we run a search and immediately attach a new file
192 /* FIXME: sometimes we receive loadingFinished before searchProgressed
193 * -> deadlock in the test.
194 filteredData_->runSearch( QRegularExpression( "123" ) );
195 waitSearchProgressed();
196 signalSearchProgressedRead();
197 logData_->attachFile( TMPDIR "/mediumlog.txt" );
198
199 // We don't expect meaningful results but it should not crash!
200 for ( int i = 0; i < 1; i++ ) {
201 waitSearchProgressed();
202 signalSearchProgressedRead();
203 }
204 */
205
206 sleep(10);
207
208 QApplication::quit();
209 }
210
updateSearch()211 void TestLogFilteredData::updateSearch()
212 {
213 logData_ = new LogData();
214
215 // Register for notification file is loaded
216 connect( logData_, SIGNAL( loadingFinished( bool ) ),
217 this, SLOT( loadingFinished() ) );
218
219 filteredData_ = logData_->getNewFilteredData();
220 connect( filteredData_, SIGNAL( searchProgressed( int, int ) ),
221 this, SLOT( searchProgressed( int, int ) ) );
222
223 QFuture<void> future = QtConcurrent::run(this, &TestLogFilteredData::updateSearchTest);
224
225 QApplication::exec();
226
227 disconnect( filteredData_, 0 );
228 disconnect( logData_, 0 );
229
230 delete filteredData_;
231 delete logData_;
232 }
233
updateSearchTest()234 void TestLogFilteredData::updateSearchTest()
235 {
236 // First load the tests file
237 logData_->attachFile( TMPDIR "/smalllog.txt" );
238 // Wait for the loading to be done
239 waitLoadingFinished();
240 QCOMPARE( logData_->getNbLine(), SL_NB_LINES );
241 signalLoadingFinishedRead();
242
243 // Perform a first search
244 filteredData_->runSearch( QRegularExpression( "123" ) );
245
246 for ( int i = 0; i < 2; i++ ) {
247 waitSearchProgressed();
248 signalSearchProgressedRead();
249 }
250
251 // Check the result
252 QCOMPARE( filteredData_->getNbLine(), 12LL );
253
254 sleep(1);
255
256 QWARN("Starting stage 2");
257
258 // Add some data to the file
259 char newLine[90];
260 QFile file( TMPDIR "/smalllog.txt" );
261 if ( file.open( QIODevice::Append ) ) {
262 for (int i = 0; i < 3000; i++) {
263 snprintf(newLine, 89, sl_format, i);
264 file.write( newLine, qstrlen(newLine) );
265 }
266 // To test the edge case when the final line is not complete and matching
267 file.write( partial_line_begin, qstrlen( partial_line_begin ) );
268 }
269 file.close();
270
271 // Let the system do the update (there might be several ones)
272 do {
273 waitLoadingFinished();
274 signalLoadingFinishedRead();
275 } while ( logData_->getNbLine() < 5001LL );
276
277 sleep(1);
278
279 // Start an update search
280 filteredData_->updateSearch();
281
282 for ( int i = 0; i < 2; i++ ) {
283 waitSearchProgressed();
284 signalSearchProgressedRead();
285 }
286
287 // Check the result
288 QCOMPARE( logData_->getNbLine(), 5001LL );
289 QCOMPARE( filteredData_->getNbLine(), 26LL );
290
291 QWARN("Starting stage 3");
292
293 // Add a couple more lines, including the end of the unfinished one.
294 if ( file.open( QIODevice::Append ) ) {
295 file.write( partial_line_end, qstrlen( partial_line_end ) );
296 for (int i = 0; i < 20; i++) {
297 snprintf(newLine, 89, sl_format, i);
298 file.write( newLine, qstrlen(newLine) );
299 }
300 // To test the edge case when the final line is not complete and not matching
301 file.write( partial_nonmatching_line_begin,
302 qstrlen( partial_nonmatching_line_begin ) );
303 }
304 file.close();
305
306 // Let the system do the update
307 waitLoadingFinished();
308 signalLoadingFinishedRead();
309
310 // Start an update search
311 filteredData_->updateSearch();
312
313 for ( int i = 0; i < 2; i++ ) {
314 waitSearchProgressed();
315 signalSearchProgressedRead();
316 }
317
318 // Check the result
319 QCOMPARE( logData_->getNbLine(), 5022LL );
320 QCOMPARE( filteredData_->getNbLine(), 26LL );
321
322 QWARN("Starting stage 4");
323
324 // Now test the case where a match is found at the end of an updated line.
325 if ( file.open( QIODevice::Append ) ) {
326 file.write( partial_line_end, qstrlen( partial_line_end ) );
327 for (int i = 0; i < 20; i++) {
328 snprintf(newLine, 89, sl_format, i);
329 file.write( newLine, qstrlen(newLine) );
330 }
331 }
332 file.close();
333
334 // Let the system do the update
335 waitLoadingFinished();
336 signalLoadingFinishedRead();
337
338 // Start an update search
339 filteredData_->updateSearch();
340
341 for ( int i = 0; i < 2; i++ ) {
342 waitSearchProgressed();
343 signalSearchProgressedRead();
344 }
345
346 // Check the result
347 QCOMPARE( logData_->getNbLine(), 5042LL );
348 QCOMPARE( filteredData_->getNbLine(), 27LL );
349
350 QApplication::quit();
351 }
352
marks()353 void TestLogFilteredData::marks()
354 {
355 logData_ = new LogData();
356
357 // Register for notification file is loaded
358 connect( logData_, SIGNAL( loadingFinished( bool ) ),
359 this, SLOT( loadingFinished() ) );
360
361 filteredData_ = logData_->getNewFilteredData();
362 connect( filteredData_, SIGNAL( searchProgressed( int, int ) ),
363 this, SLOT( searchProgressed( int, int ) ) );
364
365 QFuture<void> future = QtConcurrent::run(this, &TestLogFilteredData::marksTest);
366
367 QApplication::exec();
368
369 disconnect( filteredData_, 0 );
370 disconnect( logData_, 0 );
371
372 delete filteredData_;
373 delete logData_;
374 }
375
marksTest()376 void TestLogFilteredData::marksTest()
377 {
378 // First load the tests file
379 logData_->attachFile( TMPDIR "/smalllog.txt" );
380 // Wait for the loading to be done
381 waitLoadingFinished();
382 QCOMPARE( logData_->getNbLine(), SL_NB_LINES );
383 signalLoadingFinishedRead();
384
385 // First check no line is marked
386 for ( int i = 0; i < SL_NB_LINES; i++ )
387 QVERIFY( filteredData_->isLineMarked( i ) == false );
388
389 // Try to create some "out of limit" marks
390 filteredData_->addMark( -10 );
391 filteredData_->addMark( SL_NB_LINES + 25 );
392
393 // Check no line is marked still
394 for ( int i = 0; i < SL_NB_LINES; i++ )
395 QVERIFY( filteredData_->isLineMarked( i ) == false );
396
397 // Create a couple of unnamed marks
398 filteredData_->addMark( 10 );
399 filteredData_->addMark( 44 ); // This one will also be a match
400 filteredData_->addMark( 25 );
401
402 // Check they are marked
403 QVERIFY( filteredData_->isLineMarked( 10 ) );
404 QVERIFY( filteredData_->isLineMarked( 25 ) );
405 QVERIFY( filteredData_->isLineMarked( 44 ) );
406
407 // But others are not
408 QVERIFY( filteredData_->isLineMarked( 15 ) == false );
409 QVERIFY( filteredData_->isLineMarked( 20 ) == false );
410
411 QCOMPARE( filteredData_->getNbLine(), 3LL );
412
413 // Performs a search
414 QSignalSpy progressSpy( filteredData_,
415 SIGNAL( searchProgressed( int, int ) ) );
416 filteredData_->runSearch( QRegularExpression( "0000.4" ) );
417
418 for ( int i = 0; i < 1; i++ ) {
419 waitSearchProgressed();
420 signalSearchProgressedRead();
421 }
422
423 // We should have the result of the search and the marks
424 waitSearchProgressed();
425 QCOMPARE( filteredData_->getNbLine(), 12LL );
426 signalSearchProgressedRead();
427
428 QString startline = "LOGDATA is a part of glogg, we are going to test it thoroughly, this is line ";
429
430 QCOMPARE( filteredData_->getLineString(0), startline + "000004" );
431 QCOMPARE( filteredData_->getLineString(1), startline + "000010" );
432 QCOMPARE( filteredData_->getLineString(2), startline + "000014" );
433 QCOMPARE( filteredData_->getLineString(3), startline + "000024" );
434 QCOMPARE( filteredData_->getLineString(4), startline + "000025" );
435 QCOMPARE( filteredData_->getLineString(5), startline + "000034" );
436 QCOMPARE( filteredData_->getLineString(6), startline + "000044" );
437 QCOMPARE( filteredData_->getLineString(7), startline + "000054" );
438 QCOMPARE( filteredData_->getLineString(8), startline + "000064" );
439 QCOMPARE( filteredData_->getLineString(9), startline + "000074" );
440 QCOMPARE( filteredData_->getLineString(10), startline + "000084" );
441 QCOMPARE( filteredData_->getLineString(11), startline + "000094" );
442
443 filteredData_->setVisibility( LogFilteredData::MatchesOnly );
444
445 QCOMPARE( filteredData_->getNbLine(), 10LL );
446 QCOMPARE( filteredData_->getLineString(0), startline + "000004" );
447 QCOMPARE( filteredData_->getLineString(1), startline + "000014" );
448 QCOMPARE( filteredData_->getLineString(2), startline + "000024" );
449 QCOMPARE( filteredData_->getLineString(3), startline + "000034" );
450 QCOMPARE( filteredData_->getLineString(4), startline + "000044" );
451 QCOMPARE( filteredData_->getLineString(5), startline + "000054" );
452 QCOMPARE( filteredData_->getLineString(6), startline + "000064" );
453 QCOMPARE( filteredData_->getLineString(7), startline + "000074" );
454 QCOMPARE( filteredData_->getLineString(8), startline + "000084" );
455 QCOMPARE( filteredData_->getLineString(9), startline + "000094" );
456
457 filteredData_->setVisibility( LogFilteredData::MarksOnly );
458
459 QCOMPARE( filteredData_->getNbLine(), 3LL );
460 QCOMPARE( filteredData_->getLineString(0), startline + "000010" );
461 QCOMPARE( filteredData_->getLineString(1), startline + "000025" );
462 QCOMPARE( filteredData_->getLineString(2), startline + "000044" );
463
464 // Another test with marks only
465 filteredData_->clearSearch();
466 filteredData_->clearMarks();
467 filteredData_->setVisibility( LogFilteredData::MarksOnly );
468
469 filteredData_->addMark(18);
470 filteredData_->addMark(19);
471 filteredData_->addMark(20);
472
473 QCOMPARE( filteredData_->getMatchingLineNumber(0), 18LL );
474 QCOMPARE( filteredData_->getMatchingLineNumber(1), 19LL );
475 QCOMPARE( filteredData_->getMatchingLineNumber(2), 20LL );
476
477 QApplication::quit();
478 }
479
lineLength()480 void TestLogFilteredData::lineLength()
481 {
482 logData_ = new LogData();
483
484 // Register for notification file is loaded
485 connect( logData_, SIGNAL( loadingFinished( bool ) ),
486 this, SLOT( loadingFinished() ) );
487
488 filteredData_ = logData_->getNewFilteredData();
489 connect( filteredData_, SIGNAL( searchProgressed( int, int ) ),
490 this, SLOT( searchProgressed( int, int ) ) );
491
492 QFuture<void> future = QtConcurrent::run(this, &TestLogFilteredData::lineLengthTest);
493
494 QApplication::exec();
495
496 disconnect( filteredData_, 0 );
497 disconnect( logData_, 0 );
498
499 delete filteredData_;
500 delete logData_;
501 }
502
lineLengthTest()503 void TestLogFilteredData::lineLengthTest()
504 {
505 // Line length tests
506
507 logData_->attachFile( TMPDIR "/length_test.txt" );
508 // Wait for the loading to be done
509 waitLoadingFinished();
510 QCOMPARE( logData_->getNbLine(), 4LL );
511 signalLoadingFinishedRead();
512
513 // Performs a search (the two middle lines matche)
514 filteredData_->setVisibility( LogFilteredData::MatchesOnly );
515 filteredData_->runSearch( QRegularExpression( "longer" ) );
516
517 std::pair<int,int> progress;
518 do {
519 progress = waitSearchProgressed();
520 signalSearchProgressedRead();
521 QWARN("progress");
522 } while ( progress.second < 100 );
523
524 filteredData_->addMark( 3 );
525
526 QCOMPARE( filteredData_->getNbLine(), 2LL );
527 QCOMPARE( filteredData_->getMaxLength(), 40 );
528
529 filteredData_->setVisibility( LogFilteredData::MarksAndMatches );
530 QCOMPARE( filteredData_->getNbLine(), 3LL );
531 QCOMPARE( filteredData_->getMaxLength(), 103 );
532
533 filteredData_->setVisibility( LogFilteredData::MarksOnly );
534 QCOMPARE( filteredData_->getNbLine(), 1LL );
535 QCOMPARE( filteredData_->getMaxLength(), 103 );
536
537 filteredData_->addMark( 0 );
538 QCOMPARE( filteredData_->getNbLine(), 2LL );
539 QCOMPARE( filteredData_->getMaxLength(), 103 );
540 filteredData_->deleteMark( 3 );
541 QCOMPARE( filteredData_->getNbLine(), 1LL );
542 QCOMPARE( filteredData_->getMaxLength(), 27 );
543
544 filteredData_->setVisibility( LogFilteredData::MarksAndMatches );
545 QCOMPARE( filteredData_->getMaxLength(), 40 );
546
547 QApplication::quit();
548 }
549
550 //
551 // Private functions
552 //
loadingFinished()553 void TestLogFilteredData::loadingFinished()
554 {
555 QMutexLocker locker( &loadingFinishedMutex_ );
556
557 QWARN("loadingFinished");
558 loadingFinished_received_ = true;
559 loadingFinished_read_ = false;
560
561 loadingFinishedCondition_.wakeOne();
562
563 // Wait for the test thread to read the signal
564 while ( ! loadingFinished_read_ )
565 loadingFinishedCondition_.wait( locker.mutex() );
566 }
567
searchProgressed(int nbMatches,int completion)568 void TestLogFilteredData::searchProgressed( int nbMatches, int completion )
569 {
570 QMutexLocker locker( &searchProgressedMutex_ );
571
572 QWARN("searchProgressed");
573 searchProgressed_received_ = true;
574 searchProgressed_read_ = false;
575
576 searchLastMatches_ = nbMatches;
577 searchLastProgress_ = completion;
578
579 searchProgressedCondition_.wakeOne();
580
581 // Wait for the test thread to read the signal
582 while ( ! searchProgressed_read_ )
583 searchProgressedCondition_.wait( locker.mutex() );
584 }
585
waitSearchProgressed()586 std::pair<int,int> TestLogFilteredData::waitSearchProgressed()
587 {
588 QMutexLocker locker( &searchProgressedMutex_ );
589
590 while ( ! searchProgressed_received_ )
591 searchProgressedCondition_.wait( locker.mutex() );
592
593 QWARN("searchProgressed Received");
594
595 return std::pair<int,int>(searchLastMatches_, searchLastProgress_);
596 }
597
waitLoadingFinished()598 void TestLogFilteredData::waitLoadingFinished()
599 {
600 QMutexLocker locker( &loadingFinishedMutex_ );
601
602 while ( ! loadingFinished_received_ )
603 loadingFinishedCondition_.wait( locker.mutex() );
604
605 QWARN("loadingFinished Received");
606 }
607
signalSearchProgressedRead()608 void TestLogFilteredData::signalSearchProgressedRead()
609 {
610 QMutexLocker locker( &searchProgressedMutex_ );
611
612 searchProgressed_received_ = false;
613 searchProgressed_read_ = true;
614 searchProgressedCondition_.wakeOne();
615 }
616
signalLoadingFinishedRead()617 void TestLogFilteredData::signalLoadingFinishedRead()
618 {
619 QMutexLocker locker( &loadingFinishedMutex_ );
620
621 loadingFinished_received_ = false;
622 loadingFinished_read_ = true;
623 loadingFinishedCondition_.wakeOne();
624 }
625
generateDataFiles()626 bool TestLogFilteredData::generateDataFiles()
627 {
628 char newLine[90];
629
630 QFile file( TMPDIR "/mediumlog.txt" );
631 if ( file.open( QIODevice::WriteOnly ) ) {
632 for (int i = 0; i < ML_NB_LINES; i++) {
633 snprintf(newLine, 89, ml_format, i);
634 file.write( newLine, qstrlen(newLine) );
635 }
636 }
637 else {
638 return false;
639 }
640 file.close();
641
642 file.setFileName( TMPDIR "/smalllog.txt" );
643 if ( file.open( QIODevice::WriteOnly ) ) {
644 for (int i = 0; i < SL_NB_LINES; i++) {
645 snprintf(newLine, 89, sl_format, i);
646 file.write( newLine, qstrlen(newLine) );
647 }
648 }
649 else {
650 return false;
651 }
652 file.close();
653
654 file.setFileName( TMPDIR "/length_test.txt" );
655 if ( file.open( QIODevice::WriteOnly ) ) {
656 file.write( "This line is 27 characters.\n" );
657 file.write( "This line is longer: 36 characters.\n" );
658 file.write( "This line is even longer: 40 characters.\n" );
659 file.write( "This line is very long, it's actually hard to count but it is\
660 probably something around 103 characters.\n" );
661 }
662 file.close();
663
664 return true;
665 }
666