Home | History | Annotate | Download | only in internal
      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