### Question:
TailableFile implements Closeable { public int tail(ByteBuffer buffer, FileChangeListener listener, int maxLines) throws IOException { int readLines = 0; while (readLines < maxLines) { FileChannel currentFile = getFileChannel(); if (currentFile == null || isFullyRead()) { return readLines; } readLines += readFile(buffer, listener, maxLines - readLines, currentFile); } return readLines; } TailableFile(File file); void deleteStateFileOnExit(); void ack(); void nak(); int tail(ByteBuffer buffer, FileChangeListener listener, int maxLines); @Override void close(); File getFile(); @Override String toString();  }### Answer:
@Test void testReadLogOneLine() throws IOException { tailableFile.tail(buffy, logListener, 1); assertThat(logListener.lines).hasSize(1); assertThat(logListener.lines.get(0)).isEqualTo("foo"); }
@Test void testReadLogTwoTimesOneLine() throws IOException { tailableFile.tail(buffy, logListener, 1); assertThat(logListener.lines).hasSize(1); assertThat(logListener.lines.get(0)).isEqualTo("foo"); tailableFile.tail(buffy, logListener, 1); assertThat(logListener.lines).hasSize(2); assertThat(logListener.lines.get(1)).isEqualTo("bar"); }
@Test void testFileRotationBeforeAck() throws Exception { Path rotatedLog = logFile.toPath().resolveSibling("log-1.log"); Files.move(logFile.toPath(), rotatedLog); try { Files.write(logFile.toPath(), List.of("quux", "corge"), CREATE_NEW); tailableFile.tail(buffy, logListener, 10); assertThat(logListener.lines).containsExactly( "foo", "bar", "baz", "qux", "quux", "corge"); } finally { Files.move(rotatedLog, logFile.toPath(), REPLACE_EXISTING); } }
@Test void testReadLog() throws IOException { assertThat(logFile.length()).isGreaterThan(0); tailableFile.tail(buffy, logListener, 5); assertThat(logListener.lines).containsExactly("foo", "bar", "baz", "qux"); }