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