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