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