xref: /glogg/tests/testlogfiltereddata.cpp (revision a1e4ce9253f8e2e2c659de30403a11daa905f8fa)
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