|
@@ -21,6 +21,7 @@ package org.apache.hadoop.fs.contract;
|
|
|
import org.apache.hadoop.conf.Configuration;
|
|
|
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
|
|
|
import org.apache.hadoop.fs.FSDataInputStream;
|
|
|
+import org.apache.hadoop.fs.FileSystem;
|
|
|
import org.apache.hadoop.fs.Path;
|
|
|
import org.apache.hadoop.io.IOUtils;
|
|
|
import org.junit.Test;
|
|
@@ -31,9 +32,9 @@ import java.io.EOFException;
|
|
|
import java.io.IOException;
|
|
|
import java.util.Random;
|
|
|
|
|
|
-import static org.apache.hadoop.fs.contract.ContractTestUtils.cleanup;
|
|
|
import static org.apache.hadoop.fs.contract.ContractTestUtils.createFile;
|
|
|
import static org.apache.hadoop.fs.contract.ContractTestUtils.dataset;
|
|
|
+import static org.apache.hadoop.fs.contract.ContractTestUtils.skip;
|
|
|
import static org.apache.hadoop.fs.contract.ContractTestUtils.touch;
|
|
|
import static org.apache.hadoop.fs.contract.ContractTestUtils.verifyRead;
|
|
|
|
|
@@ -46,7 +47,6 @@ public abstract class AbstractContractSeekTest extends AbstractFSContractTestBas
|
|
|
|
|
|
public static final int DEFAULT_RANDOM_SEEK_COUNT = 100;
|
|
|
|
|
|
- private Path testPath;
|
|
|
private Path smallSeekFile;
|
|
|
private Path zeroByteFile;
|
|
|
private FSDataInputStream instream;
|
|
@@ -56,13 +56,13 @@ public abstract class AbstractContractSeekTest extends AbstractFSContractTestBas
|
|
|
super.setup();
|
|
|
skipIfUnsupported(SUPPORTS_SEEK);
|
|
|
//delete the test directory
|
|
|
- testPath = getContract().getTestPath();
|
|
|
smallSeekFile = path("seekfile.txt");
|
|
|
zeroByteFile = path("zero.txt");
|
|
|
byte[] block = dataset(TEST_FILE_LEN, 0, 255);
|
|
|
//this file now has a simple rule: offset => value
|
|
|
- createFile(getFileSystem(), smallSeekFile, false, block);
|
|
|
- touch(getFileSystem(), zeroByteFile);
|
|
|
+ FileSystem fs = getFileSystem();
|
|
|
+ createFile(fs, smallSeekFile, true, block);
|
|
|
+ touch(fs, zeroByteFile);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -79,6 +79,21 @@ public abstract class AbstractContractSeekTest extends AbstractFSContractTestBas
|
|
|
super.teardown();
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Skip a test case if the FS doesn't support positioned readable.
|
|
|
+ * This should hold automatically if the FS supports seek, even
|
|
|
+ * if it doesn't support seeking past the EOF.
|
|
|
+ * And, because this test suite requires seek to be supported, the
|
|
|
+ * feature is automatically assumed to be true unless stated otherwise.
|
|
|
+ */
|
|
|
+ protected void assumeSupportsPositionedReadable() throws IOException {
|
|
|
+ // because this ,
|
|
|
+ if (!getContract().isSupported(SUPPORTS_POSITIONED_READABLE, true)) {
|
|
|
+ skip("Skipping as unsupported feature: "
|
|
|
+ + SUPPORTS_POSITIONED_READABLE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@Test
|
|
|
public void testSeekZeroByteFile() throws Throwable {
|
|
|
describe("seek and read a 0 byte file");
|
|
@@ -282,6 +297,7 @@ public abstract class AbstractContractSeekTest extends AbstractFSContractTestBas
|
|
|
public void testPositionedBulkReadDoesntChangePosition() throws Throwable {
|
|
|
describe(
|
|
|
"verify that a positioned read does not change the getPos() value");
|
|
|
+ assumeSupportsPositionedReadable();
|
|
|
Path testSeekFile = path("bigseekfile.txt");
|
|
|
byte[] block = dataset(65536, 0, 255);
|
|
|
createFile(getFileSystem(), testSeekFile, false, block);
|
|
@@ -290,8 +306,9 @@ public abstract class AbstractContractSeekTest extends AbstractFSContractTestBas
|
|
|
assertTrue(-1 != instream.read());
|
|
|
assertEquals(40000, instream.getPos());
|
|
|
|
|
|
- byte[] readBuffer = new byte[256];
|
|
|
- instream.read(128, readBuffer, 0, readBuffer.length);
|
|
|
+ int v = 256;
|
|
|
+ byte[] readBuffer = new byte[v];
|
|
|
+ assertEquals(v, instream.read(128, readBuffer, 0, v));
|
|
|
//have gone back
|
|
|
assertEquals(40000, instream.getPos());
|
|
|
//content is the same too
|
|
@@ -317,12 +334,11 @@ public abstract class AbstractContractSeekTest extends AbstractFSContractTestBas
|
|
|
Path randomSeekFile = path("testrandomseeks.bin");
|
|
|
createFile(getFileSystem(), randomSeekFile, false, buf);
|
|
|
Random r = new Random();
|
|
|
- FSDataInputStream stm = getFileSystem().open(randomSeekFile);
|
|
|
|
|
|
// Record the sequence of seeks and reads which trigger a failure.
|
|
|
int[] seeks = new int[10];
|
|
|
int[] reads = new int[10];
|
|
|
- try {
|
|
|
+ try (FSDataInputStream stm = getFileSystem().open(randomSeekFile)) {
|
|
|
for (int i = 0; i < limit; i++) {
|
|
|
int seekOff = r.nextInt(buf.length);
|
|
|
int toRead = r.nextInt(Math.min(buf.length - seekOff, 32000));
|
|
@@ -336,13 +352,232 @@ public abstract class AbstractContractSeekTest extends AbstractFSContractTestBas
|
|
|
sb.append("Sequence of actions:\n");
|
|
|
for (int j = 0; j < seeks.length; j++) {
|
|
|
sb.append("seek @ ").append(seeks[j]).append(" ")
|
|
|
- .append("read ").append(reads[j]).append("\n");
|
|
|
+ .append("read ").append(reads[j]).append("\n");
|
|
|
}
|
|
|
LOG.error(sb.toString());
|
|
|
throw afe;
|
|
|
- } finally {
|
|
|
- stm.close();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ @Test
|
|
|
+ public void testReadFullyZeroByteFile() throws Throwable {
|
|
|
+ describe("readFully against a 0 byte file");
|
|
|
+ assumeSupportsPositionedReadable();
|
|
|
+ instream = getFileSystem().open(zeroByteFile);
|
|
|
+ assertEquals(0, instream.getPos());
|
|
|
+ byte[] buffer = new byte[1];
|
|
|
+ instream.readFully(0, buffer, 0, 0);
|
|
|
+ assertEquals(0, instream.getPos());
|
|
|
+ // seek to 0 read 0 bytes from it
|
|
|
+ instream.seek(0);
|
|
|
+ assertEquals(0, instream.read(buffer, 0, 0));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testReadFullyPastEOFZeroByteFile() throws Throwable {
|
|
|
+ assumeSupportsPositionedReadable();
|
|
|
+ describe("readFully past the EOF of a 0 byte file");
|
|
|
+ instream = getFileSystem().open(zeroByteFile);
|
|
|
+ byte[] buffer = new byte[1];
|
|
|
+ // try to read past end of file
|
|
|
+ try {
|
|
|
+ instream.readFully(0, buffer, 0, 16);
|
|
|
+ fail("Expected an exception");
|
|
|
+ } catch (IllegalArgumentException | IndexOutOfBoundsException
|
|
|
+ | EOFException e) {
|
|
|
+ // expected
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testReadFullySmallFile() throws Throwable {
|
|
|
+ describe("readFully operations");
|
|
|
+ assumeSupportsPositionedReadable();
|
|
|
+ instream = getFileSystem().open(smallSeekFile);
|
|
|
+ byte[] buffer = new byte[256];
|
|
|
+ // expect negative length to fail
|
|
|
+ try {
|
|
|
+ instream.readFully(0, buffer, 0, -16);
|
|
|
+ fail("Expected an exception");
|
|
|
+ } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
|
|
|
+ // expected
|
|
|
+ }
|
|
|
+ // negative offset into buffer
|
|
|
+ try {
|
|
|
+ instream.readFully(0, buffer, -1, 16);
|
|
|
+ fail("Expected an exception");
|
|
|
+ } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
|
|
|
+ // expected
|
|
|
+ }
|
|
|
+ // expect negative position to fail, ideally with EOF
|
|
|
+ try {
|
|
|
+ instream.readFully(-1, buffer);
|
|
|
+ fail("Expected an exception");
|
|
|
+ } catch (EOFException e) {
|
|
|
+ handleExpectedException(e);
|
|
|
+ } catch (IOException |IllegalArgumentException | IndexOutOfBoundsException e) {
|
|
|
+ handleRelaxedException("readFully with a negative position ",
|
|
|
+ "EOFException",
|
|
|
+ e);
|
|
|
+ }
|
|
|
+
|
|
|
+ // read more than the offset allows
|
|
|
+ try {
|
|
|
+ instream.readFully(0, buffer, buffer.length - 8, 16);
|
|
|
+ fail("Expected an exception");
|
|
|
+ } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
|
|
|
+ // expected
|
|
|
+ }
|
|
|
+
|
|
|
+ // read properly
|
|
|
+ assertEquals(0, instream.getPos());
|
|
|
+ instream.readFully(0, buffer);
|
|
|
+ assertEquals(0, instream.getPos());
|
|
|
+
|
|
|
+ // now read the entire file in one go
|
|
|
+ byte[] fullFile = new byte[TEST_FILE_LEN];
|
|
|
+ instream.readFully(0, fullFile);
|
|
|
+ assertEquals(0, instream.getPos());
|
|
|
+
|
|
|
+ try {
|
|
|
+ instream.readFully(16, fullFile);
|
|
|
+ fail("Expected an exception");
|
|
|
+ } catch (EOFException e) {
|
|
|
+ handleExpectedException(e);
|
|
|
+ } catch (IOException e) {
|
|
|
+ handleRelaxedException("readFully which reads past EOF ",
|
|
|
+ "EOFException",
|
|
|
+ e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testReadFullyPastEOF() throws Throwable {
|
|
|
+ describe("readFully past the EOF of a file");
|
|
|
+ assumeSupportsPositionedReadable();
|
|
|
+ instream = getFileSystem().open(smallSeekFile);
|
|
|
+ byte[] buffer = new byte[256];
|
|
|
+
|
|
|
+ // now read past the end of the file
|
|
|
+ try {
|
|
|
+ instream.readFully(TEST_FILE_LEN + 1, buffer);
|
|
|
+ fail("Expected an exception");
|
|
|
+ } catch (EOFException e) {
|
|
|
+ handleExpectedException(e);
|
|
|
+ } catch (IOException e) {
|
|
|
+ handleRelaxedException("readFully with an offset past EOF ",
|
|
|
+ "EOFException",
|
|
|
+ e);
|
|
|
+ }
|
|
|
+ // read zero bytes from an offset past EOF.
|
|
|
+ try {
|
|
|
+ instream.readFully(TEST_FILE_LEN + 1, buffer, 0, 0);
|
|
|
+ // a zero byte read may fail-fast
|
|
|
+ LOG.info("Filesystem short-circuits 0-byte reads");
|
|
|
+ } catch (EOFException e) {
|
|
|
+ handleExpectedException(e);
|
|
|
+ } catch (IOException e) {
|
|
|
+ handleRelaxedException("readFully(0 bytes) with an offset past EOF ",
|
|
|
+ "EOFException",
|
|
|
+ e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testReadFullyZeroBytebufferPastEOF() throws Throwable {
|
|
|
+ describe("readFully zero bytes from an offset past EOF");
|
|
|
+ assumeSupportsPositionedReadable();
|
|
|
+ instream = getFileSystem().open(smallSeekFile);
|
|
|
+ byte[] buffer = new byte[256];
|
|
|
+ try {
|
|
|
+ instream.readFully(TEST_FILE_LEN + 1, buffer, 0, 0);
|
|
|
+ // a zero byte read may fail-fast
|
|
|
+ LOG.info("Filesystem short-circuits 0-byte reads");
|
|
|
+ } catch (EOFException e) {
|
|
|
+ handleExpectedException(e);
|
|
|
+ } catch (IOException e) {
|
|
|
+ handleRelaxedException("readFully(0 bytes) with an offset past EOF ",
|
|
|
+ "EOFException",
|
|
|
+ e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testReadNullBuffer() throws Throwable {
|
|
|
+ describe("try to read a null buffer ");
|
|
|
+ assumeSupportsPositionedReadable();
|
|
|
+ try (FSDataInputStream in = getFileSystem().open(smallSeekFile)) {
|
|
|
+ // Null buffer
|
|
|
+ int r = in.read(0, null, 0, 16);
|
|
|
+ fail("Expected an exception from a read into a null buffer, got " + r);
|
|
|
+ } catch (IllegalArgumentException e) {
|
|
|
+ // expected
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testReadSmallFile() throws Throwable {
|
|
|
+ describe("PositionedRead.read operations");
|
|
|
+ assumeSupportsPositionedReadable();
|
|
|
+ instream = getFileSystem().open(smallSeekFile);
|
|
|
+ byte[] buffer = new byte[256];
|
|
|
+ int r;
|
|
|
+ // expect negative length to fail
|
|
|
+ try {
|
|
|
+ r = instream.read(0, buffer, 0, -16);
|
|
|
+ fail("Expected an exception, got " + r);
|
|
|
+ } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
|
|
|
+ // expected
|
|
|
+ }
|
|
|
+ // negative offset into buffer
|
|
|
+ try {
|
|
|
+ r = instream.read(0, buffer, -1, 16);
|
|
|
+ fail("Expected an exception, got " + r);
|
|
|
+ } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
|
|
|
+ // expected
|
|
|
+ }
|
|
|
+ // negative position
|
|
|
+ try {
|
|
|
+ r = instream.read(-1, buffer, 0, 16);
|
|
|
+ fail("Expected an exception, got " + r);
|
|
|
+ } catch (EOFException e) {
|
|
|
+ handleExpectedException(e);
|
|
|
+ } catch (IOException | IllegalArgumentException | IndexOutOfBoundsException e) {
|
|
|
+ handleRelaxedException("read() with a negative position ",
|
|
|
+ "EOFException",
|
|
|
+ e);
|
|
|
+ }
|
|
|
+
|
|
|
+ // read more than the offset allows
|
|
|
+ try {
|
|
|
+ r = instream.read(0, buffer, buffer.length - 8, 16);
|
|
|
+ fail("Expected an exception, got " + r);
|
|
|
+ } catch (IllegalArgumentException | IndexOutOfBoundsException e) {
|
|
|
+ // expected
|
|
|
+ }
|
|
|
+
|
|
|
+ // read properly
|
|
|
+ assertEquals(0, instream.getPos());
|
|
|
+ instream.readFully(0, buffer);
|
|
|
+ assertEquals(0, instream.getPos());
|
|
|
+
|
|
|
+ // now read the entire file in one go
|
|
|
+ byte[] fullFile = new byte[TEST_FILE_LEN];
|
|
|
+ assertEquals(TEST_FILE_LEN,
|
|
|
+ instream.read(0, fullFile, 0, fullFile.length));
|
|
|
+ assertEquals(0, instream.getPos());
|
|
|
+
|
|
|
+ // now read past the end of the file
|
|
|
+ assertEquals(-1,
|
|
|
+ instream.read(TEST_FILE_LEN + 16, buffer, 0, 1));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testReadAtExactEOF() throws Throwable {
|
|
|
+ describe("read at the end of the file");
|
|
|
+ instream = getFileSystem().open(smallSeekFile);
|
|
|
+ instream.seek(TEST_FILE_LEN -1);
|
|
|
+ assertTrue("read at last byte", instream.read() > 0);
|
|
|
+ assertEquals("read just past EOF", -1, instream.read());
|
|
|
+ }
|
|
|
}
|