// Copyright 2010 Google Inc. All Rights Reserved.
//
// Licensed 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 com.google.enterprise.connector.util;
import com.google.enterprise.connector.util.EofFilterInputStream;
import junit.framework.TestCase;
import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Test that EofFilterInputStream protects against the
* poorly behaved Apache Commons IO AutoCloseInputStream.
* Regression test for Issue 212.
*/
public class EofFilterInputStreamTest extends TestCase {
private static final String errorMsg = "Access closed stream.";
private byte[] bytes = new byte[]{ 'a', 'b', 'c'};
private byte[] buffer = new byte[1024];
/**
* This tests the expected behavior when reading while at EOF
* on a traditional stream.
*/
public void testTraditionalEOF() throws IOException {
InputStream in =
new ClosableInputStream(new ByteArrayInputStream(bytes));
int rtn = in.read(buffer);
assertEquals(3, rtn);
// We should keep hitting EOF now.
rtn = in.read(buffer);
assertEquals(-1, rtn);
rtn = in.read(buffer, 0, buffer.length);
assertEquals(-1, rtn);
rtn = in.read();
assertEquals(-1, rtn);
rtn = in.read(buffer);
assertEquals(-1, rtn);
rtn = in.read(buffer, 0, buffer.length);
assertEquals(-1, rtn);
rtn = in.read();
assertEquals(-1, rtn);
// Now rewind to the beginning and try again.
in.reset();
rtn = in.read(buffer);
assertEquals(3, rtn);
rtn = in.read(buffer);
assertEquals(-1, rtn);
// Now close the stream. Further access should be denied.
in.close();
try {
rtn = in.read(buffer);
fail("IOException was not thrown on access to closed stream.");
} catch (IOException ioe) {
assertEquals(errorMsg, ioe.getMessage());
}
}
/**
* This tests the bad behavior of AutoCloseInputStream, which doesn't
* allow repeated reads while at EOF.
*/
public void testAutoCloseEOF() throws IOException {
InputStream in = new AutoCloseInputStream(
new ClosableInputStream(new ByteArrayInputStream(bytes)));
int rtn = in.read(buffer);
assertEquals(3, rtn);
// We should hit EOF now.
rtn = in.read(buffer);
assertEquals(-1, rtn);
// But a second attempt will throw an IOException. Bad, Bad InputStream!
try {
rtn = in.read(buffer);
fail("IOException was not thrown on access to autoclosed stream.");
} catch (IOException ioe) {
assertEquals(errorMsg, ioe.getMessage());
}
in.close();
}
/**
* This tests that the EofFilterInputStream restores the traditional
* expected behavior when reading while at EOF on an AutoClosed stream.
*/
public void testAutoCloseProtection() throws IOException {
InputStream in =
new EofFilterInputStream(new AutoCloseInputStream(
new ClosableInputStream(new ByteArrayInputStream(bytes))));
int rtn = in.read(buffer);
assertEquals(3, rtn);
// We should keep hitting EOF now.
rtn = in.read(buffer);
assertEquals(-1, rtn);
rtn = in.read(buffer, 0, buffer.length);
assertEquals(-1, rtn);
rtn = in.read();
assertEquals(-1, rtn);
rtn = in.read(buffer);
assertEquals(-1, rtn);
rtn = in.read(buffer, 0, buffer.length);
assertEquals(-1, rtn);
rtn = in.read();
assertEquals(-1, rtn);
// Now explicitly close the stream. Further access should be denied.
in.close();
try {
rtn = in.read(buffer);
fail("IOException was not thrown on access to closed stream.");
} catch (IOException ioe) {
assertEquals(errorMsg, ioe.getMessage());
}
}
/**
* EofFilterInputStream can't protect againt mark()/reset() on an
* AutoClosed stream, because the underlying resource is no longer available.
*/
public void testAutoCloseProtection2() throws IOException {
InputStream in =
new EofFilterInputStream(new AutoCloseInputStream(
new ClosableInputStream(new ByteArrayInputStream(bytes))));
int rtn = in.read(buffer);
assertEquals(3, rtn);
// We should keep hitting EOF now.
rtn = in.read(buffer);
assertEquals(-1, rtn);
rtn = in.read(buffer);
assertEquals(-1, rtn);
// Attempts to rewind should fail because the shielded resource is
// actually no longer available.
try {
in.reset();
fail("IOException was not thrown on reset of closed stream.");
} catch (IOException ioe) {
assertEquals(errorMsg, ioe.getMessage());
}
}
/**
* An InputStream that can be closed, and once closed throws IOExceptions
* when accessed. Hides the fact that ByteArrayInputStream.close() is a NOOP.
*/
private class ClosableInputStream extends FilterInputStream {
boolean isClosed;
public ClosableInputStream(InputStream in) {
super(in);
isClosed = false;
}
@Override
public int read() throws IOException {
if (isClosed) {
throw new IOException(errorMsg);
}
return super.read();
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (isClosed) {
throw new IOException(errorMsg);
}
return super.read(b, off, len);
}
@Override
public void reset() throws IOException {
if (isClosed) {
throw new IOException(errorMsg);
}
super.reset();
}
@Override
public void close() throws IOException {
isClosed = true;
super.close();
}
}
/**
* An InputStream that automatically closes upon hitting EOF,
* like the poorly behaved Apache Commons IO AutoCloseInputStream.
*/
private class AutoCloseInputStream extends FilterInputStream {
public AutoCloseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int rtn = super.read();
if (-1 == rtn) {
close();
}
return rtn;
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int rtn = super.read(b, off, len);
if (-1 == rtn) {
close();
}
return rtn;
}
}
}