1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.io.input; 18 19 import static org.junit.jupiter.api.Assertions.assertEquals; 20 import static org.junit.jupiter.api.Assertions.assertFalse; 21 import static org.junit.jupiter.api.Assertions.assertNotNull; 22 import static org.junit.jupiter.api.Assertions.assertNull; 23 import static org.junit.jupiter.api.Assertions.assertTrue; 24 import static org.junit.jupiter.api.Assertions.fail; 25 26 import java.io.BufferedOutputStream; 27 import java.io.BufferedReader; 28 import java.io.File; 29 import java.io.FileNotFoundException; 30 import java.io.IOException; 31 import java.io.InputStreamReader; 32 import java.io.OutputStreamWriter; 33 import java.io.RandomAccessFile; 34 import java.io.Writer; 35 import java.nio.charset.Charset; 36 import java.nio.charset.StandardCharsets; 37 import java.nio.file.Files; 38 import java.nio.file.StandardOpenOption; 39 import java.nio.file.attribute.FileTime; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.Collections; 43 import java.util.List; 44 import java.util.concurrent.CountDownLatch; 45 import java.util.concurrent.Executor; 46 import java.util.concurrent.Executors; 47 import java.util.concurrent.ScheduledThreadPoolExecutor; 48 import java.util.concurrent.TimeUnit; 49 50 import org.apache.commons.io.FileUtils; 51 import org.apache.commons.io.IOUtils; 52 import org.apache.commons.io.RandomAccessFileMode; 53 import org.apache.commons.io.TestResources; 54 import org.apache.commons.io.test.TestUtils; 55 import org.junit.jupiter.api.Test; 56 import org.junit.jupiter.api.io.TempDir; 57 58 /** 59 * Test for {@link Tailer}. 60 */ 61 public class TailerTest { 62 63 private static final class NonStandardTailable implements Tailer.Tailable { 64 65 private final File file; 66 NonStandardTailable(final File file)67 public NonStandardTailable(final File file) { 68 this.file = file; 69 } 70 71 @Override getRandomAccess(final String mode)72 public Tailer.RandomAccessResourceBridge getRandomAccess(final String mode) throws FileNotFoundException { 73 return new Tailer.RandomAccessResourceBridge() { 74 75 private final RandomAccessFile randomAccessFile = new RandomAccessFile(file, mode); 76 77 @Override 78 public void close() throws IOException { 79 randomAccessFile.close(); 80 } 81 82 @Override 83 public long getPointer() throws IOException { 84 return randomAccessFile.getFilePointer(); 85 } 86 87 @Override 88 public int read(final byte[] b) throws IOException { 89 return randomAccessFile.read(b); 90 } 91 92 @Override 93 public void seek(final long position) throws IOException { 94 randomAccessFile.seek(position); 95 } 96 }; 97 } 98 99 @Override isNewer(final FileTime fileTime)100 public boolean isNewer(final FileTime fileTime) throws IOException { 101 return FileUtils.isFileNewer(file, fileTime); 102 } 103 104 @Override lastModifiedFileTime()105 public FileTime lastModifiedFileTime() throws IOException { 106 return FileUtils.lastModifiedFileTime(file); 107 } 108 109 @Override size()110 public long size() { 111 return file.length(); 112 } 113 } 114 115 /** 116 * Test {@link TailerListener} implementation. 117 */ 118 private static final class TestTailerListener extends TailerListenerAdapter { 119 120 // Must be synchronized because it is written by one thread and read by another 121 private final List<String> lines = Collections.synchronizedList(new ArrayList<>()); 122 123 private final CountDownLatch latch; 124 125 volatile Exception exception; 126 127 volatile int notFound; 128 129 volatile int rotated; 130 131 volatile int initialized; 132 133 volatile int reachedEndOfFile; 134 TestTailerListener()135 public TestTailerListener() { 136 latch = new CountDownLatch(1); 137 } 138 TestTailerListener(final int expectedLines)139 public TestTailerListener(final int expectedLines) { 140 latch = new CountDownLatch(expectedLines); 141 } 142 awaitExpectedLines(final long timeout, final TimeUnit timeUnit)143 public boolean awaitExpectedLines(final long timeout, final TimeUnit timeUnit) throws InterruptedException { 144 return latch.await(timeout, timeUnit); 145 } 146 clear()147 public void clear() { 148 lines.clear(); 149 } 150 151 @Override endOfFileReached()152 public void endOfFileReached() { 153 reachedEndOfFile++; // not atomic, but OK because only updated here. 154 } 155 156 @Override fileNotFound()157 public void fileNotFound() { 158 notFound++; // not atomic, but OK because only updated here. 159 } 160 161 @Override fileRotated()162 public void fileRotated() { 163 rotated++; // not atomic, but OK because only updated here. 164 } 165 getLines()166 public List<String> getLines() { 167 return lines; 168 } 169 170 @Override handle(final Exception e)171 public void handle(final Exception e) { 172 exception = e; 173 } 174 175 @Override handle(final String line)176 public void handle(final String line) { 177 lines.add(line); 178 latch.countDown(); 179 } 180 181 @Override init(final Tailer tailer)182 public void init(final Tailer tailer) { 183 initialized++; // not atomic, but OK because only updated here. 184 } 185 } 186 187 private static final int TEST_BUFFER_SIZE = 1024; 188 189 private static final int TEST_DELAY_MILLIS = 1500; 190 191 @TempDir 192 public static File temporaryFolder; 193 createFile(final File file, final long size)194 protected void createFile(final File file, final long size) throws IOException { 195 assertTrue(file.getParentFile().exists(), () -> "Cannot create file " + file + " as the parent directory does not exist"); 196 try (BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(file.toPath()))) { 197 TestUtils.generateTestData(output, size); 198 } 199 200 // try to make sure file is found 201 // (to stop continuum occasionally failing) 202 RandomAccessFile reader = null; 203 try { 204 while (reader == null) { 205 try { 206 reader = RandomAccessFileMode.READ_ONLY.create(file); 207 } catch (final FileNotFoundException ignore) { 208 // ignore 209 } 210 TestUtils.sleepQuietly(200L); 211 } 212 } finally { 213 IOUtils.closeQuietly(reader); 214 } 215 // sanity checks 216 assertTrue(file.exists()); 217 assertEquals(size, file.length()); 218 } 219 220 @Test 221 @SuppressWarnings("squid:S2699") // Suppress "Add at least one assertion to this test case" testBufferBreak()222 public void testBufferBreak() throws Exception { 223 final long delay = 50; 224 225 final File file = new File(temporaryFolder, "testBufferBreak.txt"); 226 createFile(file, 0); 227 writeString(file, "SBTOURIST\n"); 228 229 final TestTailerListener listener = new TestTailerListener(); 230 try (Tailer tailer = new Tailer(file, listener, delay, false, 1)) { 231 final Thread thread = new Thread(tailer); 232 thread.start(); 233 234 List<String> lines = listener.getLines(); 235 while (lines.isEmpty() || !lines.get(lines.size() - 1).equals("SBTOURIST")) { 236 lines = listener.getLines(); 237 } 238 239 listener.clear(); 240 } 241 } 242 243 @Test testBuilderWithNonStandardTailable()244 public void testBuilderWithNonStandardTailable() throws Exception { 245 final File file = new File(temporaryFolder, "tailer-create-with-delay-and-from-start-with-reopen-and-buffersize-and-charset.txt"); 246 createFile(file, 0); 247 final TestTailerListener listener = new TestTailerListener(1); 248 try (Tailer tailer = Tailer.builder() 249 .setExecutorService(Executors.newSingleThreadExecutor()) 250 .setTailable(new NonStandardTailable(file)) 251 .setTailerListener(listener) 252 .get()) { 253 assertTrue(tailer.getTailable() instanceof NonStandardTailable); 254 validateTailer(listener, file); 255 } 256 } 257 258 @Test testCreate()259 public void testCreate() throws Exception { 260 final File file = new File(temporaryFolder, "tailer-create.txt"); 261 createFile(file, 0); 262 final TestTailerListener listener = new TestTailerListener(1); 263 try (Tailer tailer = Tailer.create(file, listener)) { 264 validateTailer(listener, file); 265 } 266 } 267 268 @Test testCreateWithDelay()269 public void testCreateWithDelay() throws Exception { 270 final File file = new File(temporaryFolder, "tailer-create-with-delay.txt"); 271 createFile(file, 0); 272 final TestTailerListener listener = new TestTailerListener(1); 273 try (Tailer tailer = Tailer.create(file, listener, TEST_DELAY_MILLIS)) { 274 validateTailer(listener, file); 275 } 276 } 277 278 @Test testCreateWithDelayAndFromStart()279 public void testCreateWithDelayAndFromStart() throws Exception { 280 final File file = new File(temporaryFolder, "tailer-create-with-delay-and-from-start.txt"); 281 createFile(file, 0); 282 final TestTailerListener listener = new TestTailerListener(1); 283 try (Tailer tailer = Tailer.create(file, listener, TEST_DELAY_MILLIS, false)) { 284 validateTailer(listener, file); 285 } 286 } 287 288 @Test testCreateWithDelayAndFromStartWithBufferSize()289 public void testCreateWithDelayAndFromStartWithBufferSize() throws Exception { 290 final File file = new File(temporaryFolder, "tailer-create-with-delay-and-from-start-with-buffersize.txt"); 291 createFile(file, 0); 292 final TestTailerListener listener = new TestTailerListener(1); 293 try (Tailer tailer = Tailer.create(file, listener, TEST_DELAY_MILLIS, false, TEST_BUFFER_SIZE)) { 294 validateTailer(listener, file); 295 } 296 } 297 298 @Test testCreateWithDelayAndFromStartWithReopenAndBufferSize()299 public void testCreateWithDelayAndFromStartWithReopenAndBufferSize() throws Exception { 300 final File file = new File(temporaryFolder, "tailer-create-with-delay-and-from-start-with-reopen-and-buffersize.txt"); 301 createFile(file, 0); 302 final TestTailerListener listener = new TestTailerListener(1); 303 try (Tailer tailer = Tailer.create(file, listener, TEST_DELAY_MILLIS, false, true, TEST_BUFFER_SIZE)) { 304 validateTailer(listener, file); 305 } 306 } 307 308 @Test testCreateWithDelayAndFromStartWithReopenAndBufferSizeAndCharset()309 public void testCreateWithDelayAndFromStartWithReopenAndBufferSizeAndCharset() throws Exception { 310 final File file = new File(temporaryFolder, "tailer-create-with-delay-and-from-start-with-reopen-and-buffersize-and-charset.txt"); 311 createFile(file, 0); 312 final TestTailerListener listener = new TestTailerListener(1); 313 try (Tailer tailer = Tailer.create(file, StandardCharsets.UTF_8, listener, TEST_DELAY_MILLIS, false, true, TEST_BUFFER_SIZE)) { 314 validateTailer(listener, file); 315 } 316 } 317 318 @Test testCreatorWithDelayAndFromStartWithReopen()319 public void testCreatorWithDelayAndFromStartWithReopen() throws Exception { 320 final File file = new File(temporaryFolder, "tailer-create-with-delay-and-from-start-with-reopen.txt"); 321 createFile(file, 0); 322 final TestTailerListener listener = new TestTailerListener(1); 323 try (Tailer tailer = Tailer.create(file, listener, TEST_DELAY_MILLIS, false, false)) { 324 validateTailer(listener, file); 325 } 326 } 327 328 /* 329 * Tests [IO-357][Tailer] InterruptedException while the thread is sleeping is silently ignored. 330 */ 331 @Test testInterrupt()332 public void testInterrupt() throws Exception { 333 final File file = new File(temporaryFolder, "nosuchfile"); 334 assertFalse(file.exists(), "nosuchfile should not exist"); 335 final TestTailerListener listener = new TestTailerListener(); 336 // Use a long delay to try to make sure the test thread calls interrupt() while the tailer thread is sleeping. 337 final int delay = 1000; 338 final int idle = 50; // allow time for thread to work 339 try (Tailer tailer = new Tailer(file, listener, delay, false, IOUtils.DEFAULT_BUFFER_SIZE)) { 340 final Thread thread = new Thread(tailer); 341 thread.setDaemon(true); 342 thread.start(); 343 TestUtils.sleep(idle); 344 thread.interrupt(); 345 TestUtils.sleep(delay + idle); 346 assertNotNull(listener.exception, "Missing InterruptedException"); 347 assertTrue(listener.exception instanceof InterruptedException, "Unexpected Exception: " + listener.exception); 348 assertEquals(1, listener.initialized, "Expected init to be called"); 349 assertTrue(listener.notFound > 0, "fileNotFound should be called"); 350 assertEquals(0, listener.rotated, "fileRotated should be not be called"); 351 assertEquals(0, listener.reachedEndOfFile, "end of file never reached"); 352 } 353 } 354 355 @Test testIO335()356 public void testIO335() throws Exception { // test CR behavior 357 // Create & start the Tailer 358 final long delayMillis = 50; 359 final File file = new File(temporaryFolder, "tailer-testio334.txt"); 360 createFile(file, 0); 361 final TestTailerListener listener = new TestTailerListener(); 362 try (Tailer tailer = new Tailer(file, listener, delayMillis, false)) { 363 final Thread thread = new Thread(tailer); 364 thread.start(); 365 366 // Write some lines to the file 367 writeString(file, "CRLF\r\n", "LF\n", "CR\r", "CRCR\r\r", "trail"); 368 final long testDelayMillis = delayMillis * 10; 369 TestUtils.sleep(testDelayMillis); 370 final List<String> lines = listener.getLines(); 371 assertEquals(4, lines.size(), "line count"); 372 assertEquals("CRLF", lines.get(0), "line 1"); 373 assertEquals("LF", lines.get(1), "line 2"); 374 assertEquals("CR", lines.get(2), "line 3"); 375 assertEquals("CRCR\r", lines.get(3), "line 4"); 376 } 377 } 378 379 @Test 380 @SuppressWarnings("squid:S2699") // Suppress "Add at least one assertion to this test case" testLongFile()381 public void testLongFile() throws Exception { 382 final long delay = 50; 383 384 final File file = new File(temporaryFolder, "testLongFile.txt"); 385 createFile(file, 0); 386 try (Writer writer = Files.newBufferedWriter(file.toPath(), StandardOpenOption.APPEND)) { 387 for (int i = 0; i < 100000; i++) { 388 writer.write("LineLineLineLineLineLineLineLineLineLine\n"); 389 } 390 writer.write("SBTOURIST\n"); 391 } 392 393 final TestTailerListener listener = new TestTailerListener(); 394 try (Tailer tailer = new Tailer(file, listener, delay, false)) { 395 396 // final long start = System.currentTimeMillis(); 397 398 final Thread thread = new Thread(tailer); 399 thread.start(); 400 401 List<String> lines = listener.getLines(); 402 while (lines.isEmpty() || !lines.get(lines.size() - 1).equals("SBTOURIST")) { 403 lines = listener.getLines(); 404 } 405 // System.out.println("Elapsed: " + (System.currentTimeMillis() - start)); 406 407 listener.clear(); 408 } 409 } 410 411 @Test testMultiByteBreak()412 public void testMultiByteBreak() throws Exception { 413 // System.out.println("testMultiByteBreak() Default charset: " + Charset.defaultCharset().displayName()); 414 final long delay = 50; 415 final File origin = TestResources.getFile("test-file-utf8.bin"); 416 final File file = new File(temporaryFolder, "testMultiByteBreak.txt"); 417 createFile(file, 0); 418 final TestTailerListener listener = new TestTailerListener(); 419 final String osname = System.getProperty("os.name"); 420 final boolean isWindows = osname.startsWith("Windows"); 421 // Need to use UTF-8 to read & write the file otherwise it can be corrupted (depending on the default charset) 422 final Charset charsetUTF8 = StandardCharsets.UTF_8; 423 try (Tailer tailer = new Tailer(file, charsetUTF8, listener, delay, false, isWindows, IOUtils.DEFAULT_BUFFER_SIZE)) { 424 final Thread thread = new Thread(tailer); 425 thread.start(); 426 427 try (Writer out = new OutputStreamWriter(Files.newOutputStream(file.toPath()), charsetUTF8); 428 BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(origin.toPath()), charsetUTF8))) { 429 final List<String> lines = new ArrayList<>(); 430 String line; 431 while ((line = reader.readLine()) != null) { 432 out.write(line); 433 out.write("\n"); 434 lines.add(line); 435 } 436 out.close(); // ensure data is written 437 438 final long testDelayMillis = delay * 10; 439 TestUtils.sleep(testDelayMillis); 440 final List<String> tailerlines = listener.getLines(); 441 assertEquals(lines.size(), tailerlines.size(), "line count"); 442 for (int i = 0, len = lines.size(); i < len; i++) { 443 final String expected = lines.get(i); 444 final String actual = tailerlines.get(i); 445 if (!expected.equals(actual)) { 446 fail("Line: " + i + "\nExp: (" + expected.length() + ") " + expected + "\nAct: (" + actual.length() + ") " + actual); 447 } 448 } 449 } 450 } 451 } 452 453 @Test testSimpleConstructor()454 public void testSimpleConstructor() throws Exception { 455 final File file = new File(temporaryFolder, "tailer-simple-constructor.txt"); 456 createFile(file, 0); 457 final TestTailerListener listener = new TestTailerListener(1); 458 try (Tailer tailer = new Tailer(file, listener)) { 459 final Thread thread = new Thread(tailer); 460 thread.start(); 461 validateTailer(listener, file); 462 } 463 } 464 465 @Test testSimpleConstructorWithDelay()466 public void testSimpleConstructorWithDelay() throws Exception { 467 final File file = new File(temporaryFolder, "tailer-simple-constructor-with-delay.txt"); 468 createFile(file, 0); 469 final TestTailerListener listener = new TestTailerListener(1); 470 try (Tailer tailer = new Tailer(file, listener, TEST_DELAY_MILLIS)) { 471 final Thread thread = new Thread(tailer); 472 thread.start(); 473 validateTailer(listener, file); 474 } 475 } 476 477 @Test testSimpleConstructorWithDelayAndFromStart()478 public void testSimpleConstructorWithDelayAndFromStart() throws Exception { 479 final File file = new File(temporaryFolder, "tailer-simple-constructor-with-delay-and-from-start.txt"); 480 createFile(file, 0); 481 final TestTailerListener listener = new TestTailerListener(1); 482 try (Tailer tailer = new Tailer(file, listener, TEST_DELAY_MILLIS, false)) { 483 final Thread thread = new Thread(tailer); 484 thread.start(); 485 validateTailer(listener, file); 486 } 487 } 488 489 @Test testSimpleConstructorWithDelayAndFromStartWithBufferSize()490 public void testSimpleConstructorWithDelayAndFromStartWithBufferSize() throws Exception { 491 final File file = new File(temporaryFolder, "tailer-simple-constructor-with-delay-and-from-start-with-buffersize.txt"); 492 createFile(file, 0); 493 final TestTailerListener listener = new TestTailerListener(1); 494 try (Tailer tailer = new Tailer(file, listener, TEST_DELAY_MILLIS, false, TEST_BUFFER_SIZE)) { 495 final Thread thread = new Thread(tailer); 496 thread.start(); 497 validateTailer(listener, file); 498 } 499 } 500 501 @Test testSimpleConstructorWithDelayAndFromStartWithReopen()502 public void testSimpleConstructorWithDelayAndFromStartWithReopen() throws Exception { 503 final File file = new File(temporaryFolder, "tailer-simple-constructor-with-delay-and-from-start-with-reopen.txt"); 504 createFile(file, 0); 505 final TestTailerListener listener = new TestTailerListener(1); 506 try (Tailer tailer = new Tailer(file, listener, TEST_DELAY_MILLIS, false, false)) { 507 final Thread thread = new Thread(tailer); 508 thread.start(); 509 validateTailer(listener, file); 510 } 511 } 512 513 @Test testSimpleConstructorWithDelayAndFromStartWithReopenAndBufferSize()514 public void testSimpleConstructorWithDelayAndFromStartWithReopenAndBufferSize() throws Exception { 515 final File file = new File(temporaryFolder, "tailer-simple-constructor-with-delay-and-from-start-with-reopen-and-buffersize.txt"); 516 createFile(file, 0); 517 final TestTailerListener listener = new TestTailerListener(1); 518 try (Tailer tailer = new Tailer(file, listener, TEST_DELAY_MILLIS, false, true, TEST_BUFFER_SIZE)) { 519 final Thread thread = new Thread(tailer); 520 thread.start(); 521 validateTailer(listener, file); 522 } 523 } 524 525 @Test testSimpleConstructorWithDelayAndFromStartWithReopenAndBufferSizeAndCharset()526 public void testSimpleConstructorWithDelayAndFromStartWithReopenAndBufferSizeAndCharset() throws Exception { 527 final File file = new File(temporaryFolder, "tailer-simple-constructor-with-delay-and-from-start-with-reopen-and-buffersize-and-charset.txt"); 528 createFile(file, 0); 529 final TestTailerListener listener = new TestTailerListener(1); 530 try (Tailer tailer = new Tailer(file, StandardCharsets.UTF_8, listener, TEST_DELAY_MILLIS, false, true, TEST_BUFFER_SIZE)) { 531 final Thread thread = new Thread(tailer); 532 thread.start(); 533 validateTailer(listener, file); 534 } 535 } 536 537 @Test testStopWithNoFile()538 public void testStopWithNoFile() throws Exception { 539 final File file = new File(temporaryFolder, "nosuchfile"); 540 assertFalse(file.exists(), "nosuchfile should not exist"); 541 final TestTailerListener listener = new TestTailerListener(); 542 final int delay = 100; 543 final int idle = 50; // allow time for thread to work 544 try (Tailer tailer = Tailer.create(file, listener, delay, false)) { 545 TestUtils.sleep(idle); 546 } 547 TestUtils.sleep(delay + idle); 548 if (listener.exception != null) { 549 listener.exception.printStackTrace(); 550 } 551 assertNull(listener.exception, "Should not generate Exception"); 552 assertEquals(1, listener.initialized, "Expected init to be called"); 553 assertTrue(listener.notFound > 0, "fileNotFound should be called"); 554 assertEquals(0, listener.rotated, "fileRotated should be not be called"); 555 assertEquals(0, listener.reachedEndOfFile, "end of file never reached"); 556 } 557 558 @Test testStopWithNoFileUsingExecutor()559 public void testStopWithNoFileUsingExecutor() throws Exception { 560 final File file = new File(temporaryFolder, "nosuchfile"); 561 assertFalse(file.exists(), "nosuchfile should not exist"); 562 final TestTailerListener listener = new TestTailerListener(); 563 final int delay = 100; 564 final int idle = 50; // allow time for thread to work 565 try (Tailer tailer = new Tailer(file, listener, delay, false)) { 566 final Executor exec = new ScheduledThreadPoolExecutor(1); 567 exec.execute(tailer); 568 TestUtils.sleep(idle); 569 } 570 TestUtils.sleep(delay + idle); 571 assertNull(listener.exception, "Should not generate Exception"); 572 assertEquals(1, listener.initialized, "Expected init to be called"); 573 assertTrue(listener.notFound > 0, "fileNotFound should be called"); 574 assertEquals(0, listener.rotated, "fileRotated should be not be called"); 575 assertEquals(0, listener.reachedEndOfFile, "end of file never reached"); 576 } 577 578 @Test testTailer()579 public void testTailer() throws Exception { 580 581 // Create & start the Tailer 582 final long delayMillis = 50; 583 final File file = new File(temporaryFolder, "tailer1-test.txt"); 584 createFile(file, 0); 585 final TestTailerListener listener = new TestTailerListener(); 586 final String osname = System.getProperty("os.name"); 587 final boolean isWindows = osname.startsWith("Windows"); 588 try (Tailer tailer = new Tailer(file, listener, delayMillis, false, isWindows)) { 589 final Thread thread = new Thread(tailer); 590 thread.start(); 591 592 // Write some lines to the file 593 write(file, "Line one", "Line two"); 594 final long testDelayMillis = delayMillis * 10; 595 TestUtils.sleep(testDelayMillis); 596 List<String> lines = listener.getLines(); 597 assertEquals(2, lines.size(), "1 line count"); 598 assertEquals("Line one", lines.get(0), "1 line 1"); 599 assertEquals("Line two", lines.get(1), "1 line 2"); 600 listener.clear(); 601 602 // Write another line to the file 603 write(file, "Line three"); 604 TestUtils.sleep(testDelayMillis); 605 lines = listener.getLines(); 606 assertEquals(1, lines.size(), "2 line count"); 607 assertEquals("Line three", lines.get(0), "2 line 3"); 608 listener.clear(); 609 610 // Check file does actually have all the lines 611 lines = FileUtils.readLines(file, StandardCharsets.UTF_8); 612 assertEquals(3, lines.size(), "3 line count"); 613 assertEquals("Line one", lines.get(0), "3 line 1"); 614 assertEquals("Line two", lines.get(1), "3 line 2"); 615 assertEquals("Line three", lines.get(2), "3 line 3"); 616 617 // Delete & re-create 618 file.delete(); 619 assertFalse(file.exists(), "File should not exist"); 620 createFile(file, 0); 621 assertTrue(file.exists(), "File should now exist"); 622 TestUtils.sleep(testDelayMillis); 623 624 // Write another line 625 write(file, "Line four"); 626 TestUtils.sleep(testDelayMillis); 627 lines = listener.getLines(); 628 assertEquals(1, lines.size(), "4 line count"); 629 assertEquals("Line four", lines.get(0), "4 line 3"); 630 listener.clear(); 631 632 // Stop 633 thread.interrupt(); 634 TestUtils.sleep(testDelayMillis * 4); 635 write(file, "Line five"); 636 assertEquals(0, listener.getLines().size(), "4 line count"); 637 assertNotNull(listener.exception, "Missing InterruptedException"); 638 assertTrue(listener.exception instanceof InterruptedException, "Unexpected Exception: " + listener.exception); 639 assertEquals(1, listener.initialized, "Expected init to be called"); 640 // assertEquals(0 , listener.notFound, "fileNotFound should not be called"); // there is a window when it might be 641 // called 642 assertEquals(1, listener.rotated, "fileRotated should be called"); 643 } 644 } 645 646 @Test testTailerEndOfFileReached()647 public void testTailerEndOfFileReached() throws Exception { 648 // Create & start the Tailer 649 final long delayMillis = 50; 650 final long testDelayMillis = delayMillis * 10; 651 final File file = new File(temporaryFolder, "tailer-eof-test.txt"); 652 createFile(file, 0); 653 final TestTailerListener listener = new TestTailerListener(); 654 final String osname = System.getProperty("os.name"); 655 final boolean isWindows = osname.startsWith("Windows"); 656 try (Tailer tailer = new Tailer(file, listener, delayMillis, false, isWindows)) { 657 final Thread thread = new Thread(tailer); 658 thread.start(); 659 660 // write a few lines 661 write(file, "line1", "line2", "line3"); 662 TestUtils.sleep(testDelayMillis); 663 664 // write a few lines 665 write(file, "line4", "line5", "line6"); 666 TestUtils.sleep(testDelayMillis); 667 668 // write a few lines 669 write(file, "line7", "line8", "line9"); 670 TestUtils.sleep(testDelayMillis); 671 672 // May be > 3 times due to underlying OS behavior wrt streams 673 assertTrue(listener.reachedEndOfFile >= 3, "end of file reached at least 3 times"); 674 } 675 } 676 677 @Test testTailerEof()678 public void testTailerEof() throws Exception { 679 // Create & start the Tailer 680 final long delayMillis = 100; 681 final File file = new File(temporaryFolder, "tailer2-test.txt"); 682 createFile(file, 0); 683 final TestTailerListener listener = new TestTailerListener(); 684 try (Tailer tailer = new Tailer(file, listener, delayMillis, false)) { 685 final Thread thread = new Thread(tailer); 686 thread.start(); 687 688 // Write some lines to the file 689 writeString(file, "Line"); 690 691 TestUtils.sleep(delayMillis * 2); 692 List<String> lines = listener.getLines(); 693 assertEquals(0, lines.size(), "1 line count"); 694 695 writeString(file, " one\n"); 696 TestUtils.sleep(delayMillis * 4); 697 lines = listener.getLines(); 698 699 assertEquals(1, lines.size(), "1 line count"); 700 assertEquals("Line one", lines.get(0), "1 line 1"); 701 702 listener.clear(); 703 } 704 } 705 validateTailer(final TestTailerListener listener, final File file)706 private void validateTailer(final TestTailerListener listener, final File file) throws IOException, InterruptedException { 707 write(file, "foo"); 708 final int timeout = 30; 709 final TimeUnit timeoutUnit = TimeUnit.SECONDS; 710 assertTrue(listener.awaitExpectedLines(timeout, timeoutUnit), () -> String.format("await timed out after %s %s", timeout, timeoutUnit)); 711 assertEquals(listener.getLines(), Arrays.asList("foo"), "lines"); 712 } 713 714 /** Appends lines to a file */ write(final File file, final String... lines)715 private void write(final File file, final String... lines) throws IOException { 716 try (Writer writer = Files.newBufferedWriter(file.toPath(), StandardOpenOption.APPEND)) { 717 for (final String line : lines) { 718 writer.write(line + "\n"); 719 } 720 } 721 } 722 723 /** Appends strings to a file */ writeString(final File file, final String... strings)724 private void writeString(final File file, final String... strings) throws IOException { 725 try (Writer writer = Files.newBufferedWriter(file.toPath(), StandardOpenOption.APPEND)) { 726 for (final String string : strings) { 727 writer.write(string); 728 } 729 } 730 } 731 } 732