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 // Another test with marks only 469 filteredData_->clearSearch(); 470 filteredData_->clearMarks(); 471 filteredData_->setVisibility( LogFilteredData::MarksOnly ); 472 473 filteredData_->addMark(18); 474 filteredData_->addMark(19); 475 filteredData_->addMark(20); 476 477 QCOMPARE( filteredData_->getMatchingLineNumber(0), 18LL ); 478 QCOMPARE( filteredData_->getMatchingLineNumber(1), 19LL ); 479 QCOMPARE( filteredData_->getMatchingLineNumber(2), 20LL ); 480 481 QApplication::quit(); 482 } 483 484 void TestLogFilteredData::lineLength() 485 { 486 logData_ = new LogData(); 487 488 // Register for notification file is loaded 489 connect( logData_, SIGNAL( loadingFinished( bool ) ), 490 this, SLOT( loadingFinished() ) ); 491 492 filteredData_ = logData_->getNewFilteredData(); 493 connect( filteredData_, SIGNAL( searchProgressed( int, int ) ), 494 this, SLOT( searchProgressed( int, int ) ) ); 495 496 QFuture<void> future = QtConcurrent::run(this, &TestLogFilteredData::lineLengthTest); 497 498 QApplication::exec(); 499 500 disconnect( filteredData_, 0 ); 501 disconnect( logData_, 0 ); 502 503 delete filteredData_; 504 delete logData_; 505 } 506 507 void TestLogFilteredData::lineLengthTest() 508 { 509 // Line length tests 510 511 logData_->attachFile( TMPDIR "/length_test.txt" ); 512 // Wait for the loading to be done 513 waitLoadingFinished(); 514 QCOMPARE( logData_->getNbLine(), 4LL ); 515 signalLoadingFinishedRead(); 516 517 // Performs a search (the two middle lines matche) 518 filteredData_->setVisibility( LogFilteredData::MatchesOnly ); 519 filteredData_->runSearch( QRegExp( "longer" ) ); 520 521 std::pair<int,int> progress; 522 do { 523 progress = waitSearchProgressed(); 524 signalSearchProgressedRead(); 525 QWARN("progress"); 526 } while ( progress.second < 100 ); 527 528 filteredData_->addMark( 3 ); 529 530 QCOMPARE( filteredData_->getNbLine(), 2LL ); 531 QCOMPARE( filteredData_->getMaxLength(), 40 ); 532 533 filteredData_->setVisibility( LogFilteredData::MarksAndMatches ); 534 QCOMPARE( filteredData_->getNbLine(), 3LL ); 535 QCOMPARE( filteredData_->getMaxLength(), 103 ); 536 537 filteredData_->setVisibility( LogFilteredData::MarksOnly ); 538 QCOMPARE( filteredData_->getNbLine(), 1LL ); 539 QCOMPARE( filteredData_->getMaxLength(), 103 ); 540 541 filteredData_->addMark( 0 ); 542 QCOMPARE( filteredData_->getNbLine(), 2LL ); 543 QCOMPARE( filteredData_->getMaxLength(), 103 ); 544 filteredData_->deleteMark( 3 ); 545 QCOMPARE( filteredData_->getNbLine(), 1LL ); 546 QCOMPARE( filteredData_->getMaxLength(), 27 ); 547 548 filteredData_->setVisibility( LogFilteredData::MarksAndMatches ); 549 QCOMPARE( filteredData_->getMaxLength(), 40 ); 550 551 QApplication::quit(); 552 } 553 554 // 555 // Private functions 556 // 557 void TestLogFilteredData::loadingFinished() 558 { 559 QMutexLocker locker( &loadingFinishedMutex_ ); 560 561 QWARN("loadingFinished"); 562 loadingFinished_received_ = true; 563 loadingFinished_read_ = false; 564 565 loadingFinishedCondition_.wakeOne(); 566 567 // Wait for the test thread to read the signal 568 while ( ! loadingFinished_read_ ) 569 loadingFinishedCondition_.wait( locker.mutex() ); 570 } 571 572 void TestLogFilteredData::searchProgressed( int nbMatches, int completion ) 573 { 574 QMutexLocker locker( &searchProgressedMutex_ ); 575 576 QWARN("searchProgressed"); 577 searchProgressed_received_ = true; 578 searchProgressed_read_ = false; 579 580 searchLastMatches_ = nbMatches; 581 searchLastProgress_ = completion; 582 583 searchProgressedCondition_.wakeOne(); 584 585 // Wait for the test thread to read the signal 586 while ( ! searchProgressed_read_ ) 587 searchProgressedCondition_.wait( locker.mutex() ); 588 } 589 590 std::pair<int,int> TestLogFilteredData::waitSearchProgressed() 591 { 592 QMutexLocker locker( &searchProgressedMutex_ ); 593 594 while ( ! searchProgressed_received_ ) 595 searchProgressedCondition_.wait( locker.mutex() ); 596 597 QWARN("searchProgressed Received"); 598 599 return std::pair<int,int>(searchLastMatches_, searchLastProgress_); 600 } 601 602 void TestLogFilteredData::waitLoadingFinished() 603 { 604 QMutexLocker locker( &loadingFinishedMutex_ ); 605 606 while ( ! loadingFinished_received_ ) 607 loadingFinishedCondition_.wait( locker.mutex() ); 608 609 QWARN("loadingFinished Received"); 610 } 611 612 void TestLogFilteredData::signalSearchProgressedRead() 613 { 614 QMutexLocker locker( &searchProgressedMutex_ ); 615 616 searchProgressed_received_ = false; 617 searchProgressed_read_ = true; 618 searchProgressedCondition_.wakeOne(); 619 } 620 621 void TestLogFilteredData::signalLoadingFinishedRead() 622 { 623 QMutexLocker locker( &loadingFinishedMutex_ ); 624 625 loadingFinished_received_ = false; 626 loadingFinished_read_ = true; 627 loadingFinishedCondition_.wakeOne(); 628 } 629 630 bool TestLogFilteredData::generateDataFiles() 631 { 632 char newLine[90]; 633 634 QFile file( TMPDIR "/mediumlog.txt" ); 635 if ( file.open( QIODevice::WriteOnly ) ) { 636 for (int i = 0; i < ML_NB_LINES; i++) { 637 snprintf(newLine, 89, ml_format, i); 638 file.write( newLine, qstrlen(newLine) ); 639 } 640 } 641 else { 642 return false; 643 } 644 file.close(); 645 646 file.setFileName( TMPDIR "/smalllog.txt" ); 647 if ( file.open( QIODevice::WriteOnly ) ) { 648 for (int i = 0; i < SL_NB_LINES; i++) { 649 snprintf(newLine, 89, sl_format, i); 650 file.write( newLine, qstrlen(newLine) ); 651 } 652 } 653 else { 654 return false; 655 } 656 file.close(); 657 658 file.setFileName( TMPDIR "/length_test.txt" ); 659 if ( file.open( QIODevice::WriteOnly ) ) { 660 file.write( "This line is 27 characters.\n" ); 661 file.write( "This line is longer: 36 characters.\n" ); 662 file.write( "This line is even longer: 40 characters.\n" ); 663 file.write( "This line is very long, it's actually hard to count but it is\ 664 probably something around 103 characters.\n" ); 665 } 666 file.close(); 667 668 return true; 669 } 670