Home | History | Annotate | Download | only in io
      1 /*
      2  * Copyright (C) 2012 The Guava Authors
      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 
     17 package com.google.common.io;
     18 
     19 import static com.google.common.io.TestOption.CLOSE_THROWS;
     20 import static com.google.common.io.TestOption.OPEN_THROWS;
     21 import static com.google.common.io.TestOption.READ_THROWS;
     22 import static com.google.common.io.TestOption.WRITE_THROWS;
     23 
     24 import com.google.common.collect.ImmutableList;
     25 import com.google.common.collect.ImmutableSet;
     26 import com.google.common.collect.Iterables;
     27 import com.google.common.collect.Lists;
     28 import com.google.common.testing.TestLogHandler;
     29 
     30 import junit.framework.TestSuite;
     31 
     32 import java.io.BufferedReader;
     33 import java.io.IOException;
     34 import java.io.Reader;
     35 import java.io.StringWriter;
     36 import java.io.Writer;
     37 import java.util.EnumSet;
     38 import java.util.List;
     39 
     40 /**
     41  * Tests for the default implementations of {@code CharSource} methods.
     42  *
     43  * @author Colin Decker
     44  */
     45 public class CharSourceTest extends IoTestCase {
     46 
     47   public static TestSuite suite() {
     48     TestSuite suite = new TestSuite();
     49     suite.addTest(CharSourceTester.tests("CharSource.wrap[CharSequence]",
     50         SourceSinkFactories.stringCharSourceFactory()));
     51     suite.addTest(CharSourceTester.tests("CharSource.empty[]",
     52         SourceSinkFactories.emptyCharSourceFactory()));
     53     suite.addTestSuite(CharStreamsTest.class);
     54     return suite;
     55   }
     56 
     57   private static final String STRING = ASCII + I18N;
     58   private static final String LINES = "foo\nbar\r\nbaz\rsomething";
     59 
     60   private TestCharSource source;
     61 
     62   @Override
     63   public void setUp() {
     64     source = new TestCharSource(STRING);
     65   }
     66 
     67   public void testOpenBufferedStream() throws IOException {
     68     BufferedReader reader = source.openBufferedStream();
     69     assertTrue(source.wasStreamOpened());
     70     assertFalse(source.wasStreamClosed());
     71 
     72     StringWriter writer = new StringWriter();
     73     char[] buf = new char[64];
     74     int read;
     75     while ((read = reader.read(buf)) != -1) {
     76       writer.write(buf, 0, read);
     77     }
     78     reader.close();
     79     writer.close();
     80 
     81     assertTrue(source.wasStreamClosed());
     82     assertEquals(STRING, writer.toString());
     83   }
     84 
     85   public void testCopyTo_appendable() throws IOException {
     86     StringBuilder builder = new StringBuilder();
     87 
     88     assertEquals(STRING.length(), source.copyTo(builder));
     89     assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
     90 
     91     assertEquals(STRING, builder.toString());
     92   }
     93 
     94   public void testCopyTo_charSink() throws IOException {
     95     TestCharSink sink = new TestCharSink();
     96 
     97     assertFalse(sink.wasStreamOpened() || sink.wasStreamClosed());
     98 
     99     assertEquals(STRING.length(), source.copyTo(sink));
    100     assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
    101     assertTrue(sink.wasStreamOpened() && sink.wasStreamClosed());
    102 
    103     assertEquals(STRING, sink.getString());
    104   }
    105 
    106   public void testRead_toString() throws IOException {
    107     assertEquals(STRING, source.read());
    108     assertTrue(source.wasStreamOpened() && source.wasStreamClosed());
    109   }
    110 
    111   public void testReadFirstLine() throws IOException {
    112     TestCharSource lines = new TestCharSource(LINES);
    113     assertEquals("foo", lines.readFirstLine());
    114     assertTrue(lines.wasStreamOpened() && lines.wasStreamClosed());
    115   }
    116 
    117   public void testReadLines_toList() throws IOException {
    118     TestCharSource lines = new TestCharSource(LINES);
    119     assertEquals(ImmutableList.of("foo", "bar", "baz", "something"), lines.readLines());
    120     assertTrue(lines.wasStreamOpened() && lines.wasStreamClosed());
    121   }
    122 
    123   public void testReadLines_withProcessor() throws IOException {
    124     TestCharSource lines = new TestCharSource(LINES);
    125     List<String> list = lines.readLines(new LineProcessor<List<String>>() {
    126       List<String> list = Lists.newArrayList();
    127 
    128       @Override
    129       public boolean processLine(String line) throws IOException {
    130         list.add(line);
    131         return true;
    132       }
    133 
    134       @Override
    135       public List<String> getResult() {
    136         return list;
    137       }
    138     });
    139     assertEquals(ImmutableList.of("foo", "bar", "baz", "something"), list);
    140     assertTrue(lines.wasStreamOpened() && lines.wasStreamClosed());
    141   }
    142 
    143   public void testReadLines_withProcessor_stopsOnFalse() throws IOException {
    144     TestCharSource lines = new TestCharSource(LINES);
    145     List<String> list = lines.readLines(new LineProcessor<List<String>>() {
    146       List<String> list = Lists.newArrayList();
    147 
    148       @Override
    149       public boolean processLine(String line) throws IOException {
    150         list.add(line);
    151         return false;
    152       }
    153 
    154       @Override
    155       public List<String> getResult() {
    156         return list;
    157       }
    158     });
    159     assertEquals(ImmutableList.of("foo"), list);
    160     assertTrue(lines.wasStreamOpened() && lines.wasStreamClosed());
    161   }
    162 
    163   public void testCopyToAppendable_doesNotCloseIfWriter() throws IOException {
    164     TestWriter writer = new TestWriter();
    165     assertFalse(writer.closed());
    166     source.copyTo(writer);
    167     assertFalse(writer.closed());
    168   }
    169 
    170   public void testClosesOnErrors_copyingToCharSinkThatThrows() {
    171     for (TestOption option : EnumSet.of(OPEN_THROWS, WRITE_THROWS, CLOSE_THROWS)) {
    172       TestCharSource okSource = new TestCharSource(STRING);
    173       try {
    174         okSource.copyTo(new TestCharSink(option));
    175         fail();
    176       } catch (IOException expected) {
    177       }
    178       // ensure reader was closed IF it was opened (depends on implementation whether or not it's
    179       // opened at all if sink.newWriter() throws).
    180       assertTrue("stream not closed when copying to sink with option: " + option,
    181           !okSource.wasStreamOpened() || okSource.wasStreamClosed());
    182     }
    183   }
    184 
    185   public void testClosesOnErrors_whenReadThrows() {
    186     TestCharSource failSource = new TestCharSource(STRING, READ_THROWS);
    187     try {
    188       failSource.copyTo(new TestCharSink());
    189       fail();
    190     } catch (IOException expected) {
    191     }
    192     assertTrue(failSource.wasStreamClosed());
    193   }
    194 
    195   public void testClosesOnErrors_copyingToWriterThatThrows() {
    196     TestCharSource okSource = new TestCharSource(STRING);
    197     try {
    198       okSource.copyTo(new TestWriter(WRITE_THROWS));
    199       fail();
    200     } catch (IOException expected) {
    201     }
    202     assertTrue(okSource.wasStreamClosed());
    203   }
    204 
    205   public void testConcat() throws IOException {
    206     CharSource c1 = CharSource.wrap("abc");
    207     CharSource c2 = CharSource.wrap("");
    208     CharSource c3 = CharSource.wrap("de");
    209 
    210     String expected = "abcde";
    211 
    212     assertEquals(expected,
    213         CharSource.concat(ImmutableList.of(c1, c2, c3)).read());
    214     assertEquals(expected,
    215         CharSource.concat(c1, c2, c3).read());
    216     assertEquals(expected,
    217         CharSource.concat(ImmutableList.of(c1, c2, c3).iterator()).read());
    218     assertFalse(CharSource.concat(c1, c2, c3).isEmpty());
    219 
    220     CharSource emptyConcat = CharSource.concat(CharSource.empty(), CharSource.empty());
    221     assertTrue(emptyConcat.isEmpty());
    222   }
    223 
    224   public void testConcat_infiniteIterable() throws IOException {
    225     CharSource source = CharSource.wrap("abcd");
    226     Iterable<CharSource> cycle = Iterables.cycle(ImmutableList.of(source));
    227     CharSource concatenated = CharSource.concat(cycle);
    228 
    229     String expected = "abcdabcd";
    230 
    231     // read the first 8 chars manually, since there's no equivalent to ByteSource.slice
    232     // TODO(user): Add CharSource.slice?
    233     StringBuilder builder = new StringBuilder();
    234     Reader reader = concatenated.openStream(); // no need to worry about closing
    235     for (int i = 0; i < 8; i++) {
    236       builder.append((char) reader.read());
    237     }
    238     assertEquals(expected, builder.toString());
    239   }
    240 
    241   static final CharSource BROKEN_READ_SOURCE = new TestCharSource("ABC", READ_THROWS);
    242   static final CharSource BROKEN_CLOSE_SOURCE = new TestCharSource("ABC", CLOSE_THROWS);
    243   static final CharSource BROKEN_OPEN_SOURCE = new TestCharSource("ABC", OPEN_THROWS);
    244   static final CharSink BROKEN_WRITE_SINK = new TestCharSink(WRITE_THROWS);
    245   static final CharSink BROKEN_CLOSE_SINK = new TestCharSink(CLOSE_THROWS);
    246   static final CharSink BROKEN_OPEN_SINK = new TestCharSink(OPEN_THROWS);
    247 
    248   private static final ImmutableSet<CharSource> BROKEN_SOURCES
    249       = ImmutableSet.of(BROKEN_CLOSE_SOURCE, BROKEN_OPEN_SOURCE, BROKEN_READ_SOURCE);
    250   private static final ImmutableSet<CharSink> BROKEN_SINKS
    251       = ImmutableSet.of(BROKEN_CLOSE_SINK, BROKEN_OPEN_SINK, BROKEN_WRITE_SINK);
    252 
    253   public void testCopyExceptions() {
    254     if (!Closer.SuppressingSuppressor.isAvailable()) {
    255       // test that exceptions are logged
    256 
    257       TestLogHandler logHandler = new TestLogHandler();
    258       Closeables.logger.addHandler(logHandler);
    259       try {
    260         for (CharSource in : BROKEN_SOURCES) {
    261           runFailureTest(in, newNormalCharSink());
    262           assertTrue(logHandler.getStoredLogRecords().isEmpty());
    263 
    264           runFailureTest(in, BROKEN_CLOSE_SINK);
    265           assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, getAndResetRecords(logHandler));
    266         }
    267 
    268         for (CharSink out : BROKEN_SINKS) {
    269           runFailureTest(newNormalCharSource(), out);
    270           assertTrue(logHandler.getStoredLogRecords().isEmpty());
    271 
    272           runFailureTest(BROKEN_CLOSE_SOURCE, out);
    273           assertEquals(1, getAndResetRecords(logHandler));
    274         }
    275 
    276         for (CharSource in : BROKEN_SOURCES) {
    277           for (CharSink out : BROKEN_SINKS) {
    278             runFailureTest(in, out);
    279             assertTrue(getAndResetRecords(logHandler) <= 1);
    280           }
    281         }
    282       } finally {
    283         Closeables.logger.removeHandler(logHandler);
    284       }
    285     } else {
    286       // test that exceptions are suppressed
    287 
    288       for (CharSource in : BROKEN_SOURCES) {
    289         int suppressed = runSuppressionFailureTest(in, newNormalCharSink());
    290         assertEquals(0, suppressed);
    291 
    292         suppressed = runSuppressionFailureTest(in, BROKEN_CLOSE_SINK);
    293         assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, suppressed);
    294       }
    295 
    296       for (CharSink out : BROKEN_SINKS) {
    297         int suppressed = runSuppressionFailureTest(newNormalCharSource(), out);
    298         assertEquals(0, suppressed);
    299 
    300         suppressed = runSuppressionFailureTest(BROKEN_CLOSE_SOURCE, out);
    301         assertEquals(1, suppressed);
    302       }
    303 
    304       for (CharSource in : BROKEN_SOURCES) {
    305         for (CharSink out : BROKEN_SINKS) {
    306           int suppressed = runSuppressionFailureTest(in, out);
    307           assertTrue(suppressed <= 1);
    308         }
    309       }
    310     }
    311   }
    312 
    313   private static int getAndResetRecords(TestLogHandler logHandler) {
    314     int records = logHandler.getStoredLogRecords().size();
    315     logHandler.clear();
    316     return records;
    317   }
    318 
    319   private static void runFailureTest(CharSource in, CharSink out) {
    320     try {
    321       in.copyTo(out);
    322       fail();
    323     } catch (IOException expected) {
    324     }
    325   }
    326 
    327   /**
    328    * @return the number of exceptions that were suppressed on the expected thrown exception
    329    */
    330   private static int runSuppressionFailureTest(CharSource in, CharSink out) {
    331     try {
    332       in.copyTo(out);
    333       fail();
    334     } catch (IOException expected) {
    335       return CloserTest.getSuppressed(expected).length;
    336     }
    337     throw new AssertionError(); // can't happen
    338   }
    339 
    340   private static CharSource newNormalCharSource() {
    341     return CharSource.wrap("ABC");
    342   }
    343 
    344   private static CharSink newNormalCharSink() {
    345     return new CharSink() {
    346       @Override public Writer openStream() {
    347         return new StringWriter();
    348       }
    349     };
    350   }
    351 }
    352