1 /* 2 * Copyright (C) 2013 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.squareup.okhttp.internal; 17 18 import java.io.IOException; 19 import java.io.OutputStream; 20 import java.util.ArrayDeque; 21 import java.util.ArrayList; 22 import java.util.Arrays; 23 import java.util.Deque; 24 import java.util.List; 25 import org.junit.Test; 26 27 import static com.squareup.okhttp.internal.Util.UTF_8; 28 import static org.junit.Assert.assertEquals; 29 import static org.junit.Assert.assertFalse; 30 import static org.junit.Assert.assertTrue; 31 import static org.junit.Assert.fail; 32 33 public final class FaultRecoveringOutputStreamTest { 34 @Test public void noRecoveryWithoutReplacement() throws Exception { 35 FaultingOutputStream faulting = new FaultingOutputStream(); 36 TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting); 37 38 recovering.write('a'); 39 faulting.nextFault = "system on fire"; 40 try { 41 recovering.write('b'); 42 fail(); 43 } catch (IOException e) { 44 assertEquals(Arrays.asList("system on fire"), recovering.exceptionMessages); 45 assertEquals("ab", faulting.receivedUtf8); 46 assertFalse(faulting.closed); 47 } 48 } 49 50 @Test public void successfulRecoveryOnWriteFault() throws Exception { 51 FaultingOutputStream faulting1 = new FaultingOutputStream(); 52 FaultingOutputStream faulting2 = new FaultingOutputStream(); 53 TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting1); 54 recovering.replacements.addLast(faulting2); 55 56 recovering.write('a'); 57 assertEquals("a", faulting1.receivedUtf8); 58 assertEquals("", faulting2.receivedUtf8); 59 faulting1.nextFault = "system under water"; 60 recovering.write('b'); 61 assertEquals(Arrays.asList("system under water"), recovering.exceptionMessages); 62 assertEquals("ab", faulting1.receivedUtf8); 63 assertEquals("ab", faulting2.receivedUtf8); 64 assertTrue(faulting1.closed); 65 assertFalse(faulting2.closed); 66 67 // Confirm that new data goes to the new stream. 68 recovering.write('c'); 69 assertEquals("ab", faulting1.receivedUtf8); 70 assertEquals("abc", faulting2.receivedUtf8); 71 } 72 73 @Test public void successfulRecoveryOnFlushFault() throws Exception { 74 FaultingOutputStream faulting1 = new FaultingOutputStream(); 75 FaultingOutputStream faulting2 = new FaultingOutputStream(); 76 TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting1); 77 recovering.replacements.addLast(faulting2); 78 79 recovering.write('a'); 80 faulting1.nextFault = "bad weather"; 81 recovering.flush(); 82 assertEquals(Arrays.asList("bad weather"), recovering.exceptionMessages); 83 assertEquals("a", faulting1.receivedUtf8); 84 assertEquals("a", faulting2.receivedUtf8); 85 assertTrue(faulting1.closed); 86 assertFalse(faulting2.closed); 87 assertEquals("a", faulting2.flushedUtf8); 88 89 // Confirm that new data goes to the new stream. 90 recovering.write('b'); 91 assertEquals("a", faulting1.receivedUtf8); 92 assertEquals("ab", faulting2.receivedUtf8); 93 assertEquals("a", faulting2.flushedUtf8); 94 } 95 96 @Test public void successfulRecoveryOnCloseFault() throws Exception { 97 FaultingOutputStream faulting1 = new FaultingOutputStream(); 98 FaultingOutputStream faulting2 = new FaultingOutputStream(); 99 TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting1); 100 recovering.replacements.addLast(faulting2); 101 102 recovering.write('a'); 103 faulting1.nextFault = "termites"; 104 recovering.close(); 105 assertEquals(Arrays.asList("termites"), recovering.exceptionMessages); 106 assertEquals("a", faulting1.receivedUtf8); 107 assertEquals("a", faulting2.receivedUtf8); 108 assertTrue(faulting1.closed); 109 assertTrue(faulting2.closed); 110 } 111 112 @Test public void replacementStreamFaultsImmediately() throws Exception { 113 FaultingOutputStream faulting1 = new FaultingOutputStream(); 114 FaultingOutputStream faulting2 = new FaultingOutputStream(); 115 FaultingOutputStream faulting3 = new FaultingOutputStream(); 116 TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting1); 117 recovering.replacements.addLast(faulting2); 118 recovering.replacements.addLast(faulting3); 119 120 recovering.write('a'); 121 assertEquals("a", faulting1.receivedUtf8); 122 assertEquals("", faulting2.receivedUtf8); 123 assertEquals("", faulting3.receivedUtf8); 124 faulting1.nextFault = "offline"; 125 faulting2.nextFault = "slow"; 126 recovering.write('b'); 127 assertEquals(Arrays.asList("offline", "slow"), recovering.exceptionMessages); 128 assertEquals("ab", faulting1.receivedUtf8); 129 assertEquals("a", faulting2.receivedUtf8); 130 assertEquals("ab", faulting3.receivedUtf8); 131 assertTrue(faulting1.closed); 132 assertTrue(faulting2.closed); 133 assertFalse(faulting3.closed); 134 135 // Confirm that new data goes to the new stream. 136 recovering.write('c'); 137 assertEquals("ab", faulting1.receivedUtf8); 138 assertEquals("a", faulting2.receivedUtf8); 139 assertEquals("abc", faulting3.receivedUtf8); 140 } 141 142 @Test public void recoverWithFullBuffer() throws Exception { 143 FaultingOutputStream faulting1 = new FaultingOutputStream(); 144 FaultingOutputStream faulting2 = new FaultingOutputStream(); 145 TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting1); 146 recovering.replacements.addLast(faulting2); 147 148 recovering.write("abcdefghij".getBytes(UTF_8)); // 10 bytes. 149 faulting1.nextFault = "unlucky"; 150 recovering.write('k'); 151 assertEquals("abcdefghijk", faulting1.receivedUtf8); 152 assertEquals("abcdefghijk", faulting2.receivedUtf8); 153 assertEquals(Arrays.asList("unlucky"), recovering.exceptionMessages); 154 assertTrue(faulting1.closed); 155 assertFalse(faulting2.closed); 156 157 // Confirm that new data goes to the new stream. 158 recovering.write('l'); 159 assertEquals("abcdefghijk", faulting1.receivedUtf8); 160 assertEquals("abcdefghijkl", faulting2.receivedUtf8); 161 } 162 163 @Test public void noRecoveryWithOverfullBuffer() throws Exception { 164 FaultingOutputStream faulting1 = new FaultingOutputStream(); 165 FaultingOutputStream faulting2 = new FaultingOutputStream(); 166 TestFaultRecoveringOutputStream recovering = new TestFaultRecoveringOutputStream(10, faulting1); 167 recovering.replacements.addLast(faulting2); 168 169 recovering.write("abcdefghijk".getBytes(UTF_8)); // 11 bytes. 170 faulting1.nextFault = "out to lunch"; 171 try { 172 recovering.write('l'); 173 fail(); 174 } catch (IOException expected) { 175 assertEquals("out to lunch", expected.getMessage()); 176 } 177 178 assertEquals(Arrays.<String>asList(), recovering.exceptionMessages); 179 assertEquals("abcdefghijkl", faulting1.receivedUtf8); 180 assertEquals("", faulting2.receivedUtf8); 181 assertFalse(faulting1.closed); 182 assertFalse(faulting2.closed); 183 } 184 185 static class FaultingOutputStream extends OutputStream { 186 String receivedUtf8 = ""; 187 String flushedUtf8 = null; 188 String nextFault; 189 boolean closed; 190 191 @Override public final void write(int data) throws IOException { 192 write(new byte[] { (byte) data }); 193 } 194 195 @Override public void write(byte[] buffer, int offset, int count) throws IOException { 196 receivedUtf8 += new String(buffer, offset, count, UTF_8); 197 if (nextFault != null) throw new IOException(nextFault); 198 } 199 200 @Override public void flush() throws IOException { 201 flushedUtf8 = receivedUtf8; 202 if (nextFault != null) throw new IOException(nextFault); 203 } 204 205 @Override public void close() throws IOException { 206 closed = true; 207 if (nextFault != null) throw new IOException(nextFault); 208 } 209 } 210 211 static class TestFaultRecoveringOutputStream extends FaultRecoveringOutputStream { 212 final List<String> exceptionMessages = new ArrayList<String>(); 213 final Deque<OutputStream> replacements = new ArrayDeque<OutputStream>(); 214 215 TestFaultRecoveringOutputStream(int maxReplayBufferLength, OutputStream first) { 216 super(maxReplayBufferLength, first); 217 } 218 219 @Override protected OutputStream replacementStream(IOException e) { 220 exceptionMessages.add(e.getMessage()); 221 return replacements.poll(); 222 } 223 } 224 } 225