/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flume.serialization; import com.google.common.base.Charsets; import com.google.common.collect.Lists; import com.google.common.io.Files; import junit.framework.Assert; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.List; public class TestResettableFileInputStream { private static final boolean CLEANUP = true; private static final File WORK_DIR = new File("target/test/work").getAbsoluteFile(); private static final Logger logger = LoggerFactory.getLogger (TestResettableFileInputStream.class); private File file, meta; @Before public void setup() throws Exception { Files.createParentDirs(new File(WORK_DIR, "dummy")); file = File.createTempFile(getClass().getSimpleName(), ".txt", WORK_DIR); logger.info("Data file: {}", file); meta = File.createTempFile(getClass().getSimpleName(), ".avro", WORK_DIR); logger.info("PositionTracker meta file: {}", meta); meta.delete(); // We want the filename but not the empty file } @After public void tearDown() throws Exception { if (CLEANUP) { meta.delete(); file.delete(); } } /** * Ensure that we can simply read bytes from a file. * @throws IOException */ @Test public void testBasicRead() throws IOException { String output = singleLineFileInit(file, Charsets.UTF_8); PositionTracker tracker = new DurablePositionTracker(meta, file.getPath()); ResettableInputStream in = new ResettableFileInputStream(file, tracker); String result = readLine(in, output.length()); assertEquals(output, result); String afterEOF = readLine(in, output.length()); assertNull(afterEOF); in.close(); } /** * Ensure a reset() brings us back to the default mark (beginning of file) * @throws IOException */ @Test public void testReset() throws IOException { String output = singleLineFileInit(file, Charsets.UTF_8); PositionTracker tracker = new DurablePositionTracker(meta, file.getPath()); ResettableInputStream in = new ResettableFileInputStream(file, tracker); String result1 = readLine(in, output.length()); assertEquals(output, result1); in.reset(); String result2 = readLine(in, output.length()); assertEquals(output, result2); String result3 = readLine(in, output.length()); assertNull("Should be null: " + result3, result3); in.close(); } /** * Ensure that marking and resetting works. * @throws IOException */ @Test public void testMarkReset() throws IOException { List<String> expected = multiLineFileInit(file, Charsets.UTF_8); int MAX_LEN = 100; PositionTracker tracker = new DurablePositionTracker(meta, file.getPath()); ResettableInputStream in = new ResettableFileInputStream(file, tracker); String result0 = readLine(in, MAX_LEN); assertEquals(expected.get(0), result0); in.reset(); String result0a = readLine(in, MAX_LEN); assertEquals(expected.get(0), result0a); in.mark(); String result1 = readLine(in, MAX_LEN); assertEquals(expected.get(1), result1); in.reset(); String result1a = readLine(in, MAX_LEN); assertEquals(expected.get(1), result1a); in.mark(); in.close(); } @Test public void testResume() throws IOException { List<String> expected = multiLineFileInit(file, Charsets.UTF_8); int MAX_LEN = 100; PositionTracker tracker = new DurablePositionTracker(meta, file.getPath()); ResettableInputStream in = new ResettableFileInputStream(file, tracker); String result0 = readLine(in, MAX_LEN); String result1 = readLine(in, MAX_LEN); in.mark(); String result2 = readLine(in, MAX_LEN); Assert.assertEquals(expected.get(2), result2); String result3 = readLine(in, MAX_LEN); Assert.assertEquals(expected.get(3), result3); in.close(); tracker.close(); // redundant // create new Tracker & RIS tracker = new DurablePositionTracker(meta, file.getPath()); in = new ResettableFileInputStream(file, tracker); String result2a = readLine(in, MAX_LEN); String result3a = readLine(in, MAX_LEN); Assert.assertEquals(result2, result2a); Assert.assertEquals(result3, result3a); } @Test public void testSeek() throws IOException { int NUM_LINES = 1000; int LINE_LEN = 1000; generateData(file, Charsets.UTF_8, NUM_LINES, LINE_LEN); PositionTracker tracker = new DurablePositionTracker(meta, file.getPath()); ResettableInputStream in = new ResettableFileInputStream(file, tracker, 10 * LINE_LEN, Charsets.UTF_8); String line = ""; for (int i = 0; i < 9; i++) { line = readLine(in, LINE_LEN); } int lineNum = Integer.parseInt(line.substring(0, 10)); assertEquals(8, lineNum); // seek back within our buffer long pos = in.tell(); in.seek(pos - 2 * LINE_LEN); // jump back 2 lines line = readLine(in, LINE_LEN); lineNum = Integer.parseInt(line.substring(0, 10)); assertEquals(7, lineNum); // seek forward within our buffer in.seek(in.tell() + LINE_LEN); line = readLine(in, LINE_LEN); lineNum = Integer.parseInt(line.substring(0, 10)); assertEquals(9, lineNum); // seek forward outside our buffer in.seek(in.tell() + 20 * LINE_LEN); line = readLine(in, LINE_LEN); lineNum = Integer.parseInt(line.substring(0, 10)); assertEquals(30, lineNum); // seek backward outside our buffer in.seek(in.tell() - 25 * LINE_LEN); line = readLine(in, LINE_LEN); lineNum = Integer.parseInt(line.substring(0, 10)); assertEquals(6, lineNum); // test a corner-case seek which requires a buffer refill in.seek(100 * LINE_LEN); in.seek(0); // reset buffer in.seek(9 * LINE_LEN); assertEquals(9, Integer.parseInt(readLine(in, LINE_LEN).substring(0, 10))); assertEquals(10, Integer.parseInt(readLine(in, LINE_LEN).substring(0, 10))); assertEquals(11, Integer.parseInt(readLine(in, LINE_LEN).substring(0, 10))); } /** * Helper function to read a line from a character stream. * @param in * @param maxLength * @return * @throws IOException */ private static String readLine(ResettableInputStream in, int maxLength) throws IOException { StringBuilder s = new StringBuilder(); int c; int i = 1; while ((c = in.readChar()) != -1) { // FIXME: support \r\n if (c == '\n') { break; } //System.out.printf("seen char val: %c\n", (char)c); s.append((char)c); if (i++ > maxLength) { System.out.println("Output: >" + s + "<"); throw new RuntimeException("Too far!"); } } if (s.length() > 0) { s.append('\n'); return s.toString(); } else { return null; } } private static String singleLineFileInit(File file, Charset charset) throws IOException { String output = "This is gonna be great!\n"; Files.write(output.getBytes(charset), file); return output; } private static List<String> multiLineFileInit(File file, Charset charset) throws IOException { List<String> lines = Lists.newArrayList(); lines.add("1. On the planet of Mars\n"); lines.add("2. They have clothes just like ours,\n"); lines.add("3. And they have the same shoes and same laces,\n"); lines.add("4. And they have the same charms and same graces...\n"); StringBuilder sb = new StringBuilder(); for (String line : lines) { sb.append(line); } Files.write(sb.toString().getBytes(charset), file); return lines; } private static void generateData(File file, Charset charset, int numLines, int lineLen) throws IOException { OutputStream out = new BufferedOutputStream(new FileOutputStream(file)); StringBuilder junk = new StringBuilder(); for (int x = 0; x < lineLen - 13; x++) { junk.append('x'); } String payload = junk.toString(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < numLines; i++) { builder.append(String.format("%010d: %s\n", i, payload)); if (i % 1000 == 0 && i != 0) { out.write(builder.toString().getBytes(charset)); builder.setLength(0); } } out.write(builder.toString().getBytes(charset)); out.close(); Assert.assertEquals(lineLen * numLines, file.length()); } }