blob: e933c177c3fefac4e1ded0c26c650c14c54862cc [file] [log] [blame]
/*
* Copyright (C) 2013 Square, Inc.
*
* 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.squareup.okhttp.internal;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import org.junit.Test;
import static com.squareup.okhttp.internal.Util.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public final class FaultRecoveringOutputStreamTest {
@Test public void noRecoveryWithoutReplacement() throws Exception {
FaultingOutputStream faulting = new FaultingOutputStream();
TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting);
recovering.write('a');
faulting.nextFault = "system on fire";
try {
recovering.write('b');
fail();
} catch (IOException e) {
assertEquals(Arrays.asList("system on fire"), recovering.exceptionMessages);
assertEquals("ab", faulting.receivedUtf8);
assertFalse(faulting.closed);
}
}
@Test public void successfulRecoveryOnWriteFault() throws Exception {
FaultingOutputStream faulting1 = new FaultingOutputStream();
FaultingOutputStream faulting2 = new FaultingOutputStream();
TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting1);
recovering.replacements.addLast(faulting2);
recovering.write('a');
assertEquals("a", faulting1.receivedUtf8);
assertEquals("", faulting2.receivedUtf8);
faulting1.nextFault = "system under water";
recovering.write('b');
assertEquals(Arrays.asList("system under water"), recovering.exceptionMessages);
assertEquals("ab", faulting1.receivedUtf8);
assertEquals("ab", faulting2.receivedUtf8);
assertTrue(faulting1.closed);
assertFalse(faulting2.closed);
// Confirm that new data goes to the new stream.
recovering.write('c');
assertEquals("ab", faulting1.receivedUtf8);
assertEquals("abc", faulting2.receivedUtf8);
}
@Test public void successfulRecoveryOnFlushFault() throws Exception {
FaultingOutputStream faulting1 = new FaultingOutputStream();
FaultingOutputStream faulting2 = new FaultingOutputStream();
TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting1);
recovering.replacements.addLast(faulting2);
recovering.write('a');
faulting1.nextFault = "bad weather";
recovering.flush();
assertEquals(Arrays.asList("bad weather"), recovering.exceptionMessages);
assertEquals("a", faulting1.receivedUtf8);
assertEquals("a", faulting2.receivedUtf8);
assertTrue(faulting1.closed);
assertFalse(faulting2.closed);
assertEquals("a", faulting2.flushedUtf8);
// Confirm that new data goes to the new stream.
recovering.write('b');
assertEquals("a", faulting1.receivedUtf8);
assertEquals("ab", faulting2.receivedUtf8);
assertEquals("a", faulting2.flushedUtf8);
}
@Test public void successfulRecoveryOnCloseFault() throws Exception {
FaultingOutputStream faulting1 = new FaultingOutputStream();
FaultingOutputStream faulting2 = new FaultingOutputStream();
TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting1);
recovering.replacements.addLast(faulting2);
recovering.write('a');
faulting1.nextFault = "termites";
recovering.close();
assertEquals(Arrays.asList("termites"), recovering.exceptionMessages);
assertEquals("a", faulting1.receivedUtf8);
assertEquals("a", faulting2.receivedUtf8);
assertTrue(faulting1.closed);
assertTrue(faulting2.closed);
}
@Test public void replacementStreamFaultsImmediately() throws Exception {
FaultingOutputStream faulting1 = new FaultingOutputStream();
FaultingOutputStream faulting2 = new FaultingOutputStream();
FaultingOutputStream faulting3 = new FaultingOutputStream();
TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting1);
recovering.replacements.addLast(faulting2);
recovering.replacements.addLast(faulting3);
recovering.write('a');
assertEquals("a", faulting1.receivedUtf8);
assertEquals("", faulting2.receivedUtf8);
assertEquals("", faulting3.receivedUtf8);
faulting1.nextFault = "offline";
faulting2.nextFault = "slow";
recovering.write('b');
assertEquals(Arrays.asList("offline", "slow"), recovering.exceptionMessages);
assertEquals("ab", faulting1.receivedUtf8);
assertEquals("a", faulting2.receivedUtf8);
assertEquals("ab", faulting3.receivedUtf8);
assertTrue(faulting1.closed);
assertTrue(faulting2.closed);
assertFalse(faulting3.closed);
// Confirm that new data goes to the new stream.
recovering.write('c');
assertEquals("ab", faulting1.receivedUtf8);
assertEquals("a", faulting2.receivedUtf8);
assertEquals("abc", faulting3.receivedUtf8);
}
@Test public void recoverWithFullBuffer() throws Exception {
FaultingOutputStream faulting1 = new FaultingOutputStream();
FaultingOutputStream faulting2 = new FaultingOutputStream();
TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting1);
recovering.replacements.addLast(faulting2);
recovering.write("abcdefghij".getBytes(UTF_8)); // 10 bytes.
faulting1.nextFault = "unlucky";
recovering.write('k');
assertEquals("abcdefghijk", faulting1.receivedUtf8);
assertEquals("abcdefghijk", faulting2.receivedUtf8);
assertEquals(Arrays.asList("unlucky"), recovering.exceptionMessages);
assertTrue(faulting1.closed);
assertFalse(faulting2.closed);
// Confirm that new data goes to the new stream.
recovering.write('l');
assertEquals("abcdefghijk", faulting1.receivedUtf8);
assertEquals("abcdefghijkl", faulting2.receivedUtf8);
}
@Test public void noRecoveryWithOverfullBuffer() throws Exception {
FaultingOutputStream faulting1 = new FaultingOutputStream();
FaultingOutputStream faulting2 = new FaultingOutputStream();
TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting1);
recovering.replacements.addLast(faulting2);
recovering.write("abcdefghijk".getBytes(UTF_8)); // 11 bytes.
faulting1.nextFault = "out to lunch";
try {
recovering.write('l');
fail();
} catch (IOException expected) {
assertEquals("out to lunch", expected.getMessage());
}
assertEquals(Arrays.<String>asList(), recovering.exceptionMessages);
assertEquals("abcdefghijkl", faulting1.receivedUtf8);
assertEquals("", faulting2.receivedUtf8);
assertFalse(faulting1.closed);
assertFalse(faulting2.closed);
}
static class FaultingOutputStream extends OutputStream {
String receivedUtf8 = "";
String flushedUtf8 = null;
String nextFault;
boolean closed;
@Override public final void write(int data) throws IOException {
write(new byte[] { (byte) data });
}
@Override public void write(byte[] buffer, int offset, int count) throws IOException {
receivedUtf8 += new String(buffer, offset, count, UTF_8);
if (nextFault != null) throw new IOException(nextFault);
}
@Override public void flush() throws IOException {
flushedUtf8 = receivedUtf8;
if (nextFault != null) throw new IOException(nextFault);
}
@Override public void close() throws IOException {
closed = true;
if (nextFault != null) throw new IOException(nextFault);
}
}
static class TestFaultRecoveringOutputStream extends FaultRecoveringOutputStream {
final List<String> exceptionMessages = new ArrayList<String>();
final Deque<OutputStream> replacements = new ArrayDeque<OutputStream>();
TestFaultRecoveringOutputStream(int maxReplayBufferLength, OutputStream first) {
super(maxReplayBufferLength, first);
}
@Override protected OutputStream replacementStream(IOException e) {
exceptionMessages.add(e.getMessage());
return replacements.poll();
}
}
}