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