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.io.Closer.LoggingSuppressor; 30 import com.google.common.testing.TestLogHandler; 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 try { 214 okSource.copyTo(new TestCharSink(option)); 215 fail(); 216 } catch (IOException expected) { 217 } 218 // ensure reader was closed IF it was opened (depends on implementation whether or not it's 219 // opened at all if sink.newWriter() throws). 220 assertTrue( 221 "stream not closed when copying to sink with option: " + option, 222 !okSource.wasStreamOpened() || okSource.wasStreamClosed()); 223 } 224 } 225 testClosesOnErrors_whenReadThrows()226 public void testClosesOnErrors_whenReadThrows() { 227 TestCharSource failSource = new TestCharSource(STRING, READ_THROWS); 228 try { 229 failSource.copyTo(new TestCharSink()); 230 fail(); 231 } catch (IOException expected) { 232 } 233 assertTrue(failSource.wasStreamClosed()); 234 } 235 testClosesOnErrors_copyingToWriterThatThrows()236 public void testClosesOnErrors_copyingToWriterThatThrows() { 237 TestCharSource okSource = new TestCharSource(STRING); 238 try { 239 okSource.copyTo(new TestWriter(WRITE_THROWS)); 240 fail(); 241 } catch (IOException expected) { 242 } 243 assertTrue(okSource.wasStreamClosed()); 244 } 245 testConcat()246 public void testConcat() throws IOException { 247 CharSource c1 = CharSource.wrap("abc"); 248 CharSource c2 = CharSource.wrap(""); 249 CharSource c3 = CharSource.wrap("de"); 250 251 String expected = "abcde"; 252 253 assertEquals(expected, CharSource.concat(ImmutableList.of(c1, c2, c3)).read()); 254 assertEquals(expected, CharSource.concat(c1, c2, c3).read()); 255 assertEquals(expected, CharSource.concat(ImmutableList.of(c1, c2, c3).iterator()).read()); 256 assertFalse(CharSource.concat(c1, c2, c3).isEmpty()); 257 258 CharSource emptyConcat = CharSource.concat(CharSource.empty(), CharSource.empty()); 259 assertTrue(emptyConcat.isEmpty()); 260 } 261 testConcat_infiniteIterable()262 public void testConcat_infiniteIterable() throws IOException { 263 CharSource source = CharSource.wrap("abcd"); 264 Iterable<CharSource> cycle = Iterables.cycle(ImmutableList.of(source)); 265 CharSource concatenated = CharSource.concat(cycle); 266 267 String expected = "abcdabcd"; 268 269 // read the first 8 chars manually, since there's no equivalent to ByteSource.slice 270 // TODO(cgdecker): Add CharSource.slice? 271 StringBuilder builder = new StringBuilder(); 272 Reader reader = concatenated.openStream(); // no need to worry about closing 273 for (int i = 0; i < 8; i++) { 274 builder.append((char) reader.read()); 275 } 276 assertEquals(expected, builder.toString()); 277 } 278 279 static final CharSource BROKEN_READ_SOURCE = new TestCharSource("ABC", READ_THROWS); 280 static final CharSource BROKEN_CLOSE_SOURCE = new TestCharSource("ABC", CLOSE_THROWS); 281 static final CharSource BROKEN_OPEN_SOURCE = new TestCharSource("ABC", OPEN_THROWS); 282 static final CharSink BROKEN_WRITE_SINK = new TestCharSink(WRITE_THROWS); 283 static final CharSink BROKEN_CLOSE_SINK = new TestCharSink(CLOSE_THROWS); 284 static final CharSink BROKEN_OPEN_SINK = new TestCharSink(OPEN_THROWS); 285 286 private static final ImmutableSet<CharSource> BROKEN_SOURCES = 287 ImmutableSet.of(BROKEN_CLOSE_SOURCE, BROKEN_OPEN_SOURCE, BROKEN_READ_SOURCE); 288 private static final ImmutableSet<CharSink> BROKEN_SINKS = 289 ImmutableSet.of(BROKEN_CLOSE_SINK, BROKEN_OPEN_SINK, BROKEN_WRITE_SINK); 290 testCopyExceptions()291 public void testCopyExceptions() { 292 if (Closer.create().suppressor instanceof LoggingSuppressor) { 293 // test that exceptions are logged 294 295 TestLogHandler logHandler = new TestLogHandler(); 296 Closeables.logger.addHandler(logHandler); 297 try { 298 for (CharSource in : BROKEN_SOURCES) { 299 runFailureTest(in, newNormalCharSink()); 300 assertTrue(logHandler.getStoredLogRecords().isEmpty()); 301 302 runFailureTest(in, BROKEN_CLOSE_SINK); 303 assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, getAndResetRecords(logHandler)); 304 } 305 306 for (CharSink out : BROKEN_SINKS) { 307 runFailureTest(newNormalCharSource(), out); 308 assertTrue(logHandler.getStoredLogRecords().isEmpty()); 309 310 runFailureTest(BROKEN_CLOSE_SOURCE, out); 311 assertEquals(1, getAndResetRecords(logHandler)); 312 } 313 314 for (CharSource in : BROKEN_SOURCES) { 315 for (CharSink out : BROKEN_SINKS) { 316 runFailureTest(in, out); 317 assertTrue(getAndResetRecords(logHandler) <= 1); 318 } 319 } 320 } finally { 321 Closeables.logger.removeHandler(logHandler); 322 } 323 } else { 324 // test that exceptions are suppressed 325 326 for (CharSource in : BROKEN_SOURCES) { 327 int suppressed = runSuppressionFailureTest(in, newNormalCharSink()); 328 assertEquals(0, suppressed); 329 330 suppressed = runSuppressionFailureTest(in, BROKEN_CLOSE_SINK); 331 assertEquals((in == BROKEN_OPEN_SOURCE) ? 0 : 1, suppressed); 332 } 333 334 for (CharSink out : BROKEN_SINKS) { 335 int suppressed = runSuppressionFailureTest(newNormalCharSource(), out); 336 assertEquals(0, suppressed); 337 338 suppressed = runSuppressionFailureTest(BROKEN_CLOSE_SOURCE, out); 339 assertEquals(1, suppressed); 340 } 341 342 for (CharSource in : BROKEN_SOURCES) { 343 for (CharSink out : BROKEN_SINKS) { 344 int suppressed = runSuppressionFailureTest(in, out); 345 assertTrue(suppressed <= 1); 346 } 347 } 348 } 349 } 350 getAndResetRecords(TestLogHandler logHandler)351 private static int getAndResetRecords(TestLogHandler logHandler) { 352 int records = logHandler.getStoredLogRecords().size(); 353 logHandler.clear(); 354 return records; 355 } 356 runFailureTest(CharSource in, CharSink out)357 private static void runFailureTest(CharSource in, CharSink out) { 358 try { 359 in.copyTo(out); 360 fail(); 361 } catch (IOException expected) { 362 } 363 } 364 365 /** @return the number of exceptions that were suppressed on the expected thrown exception */ runSuppressionFailureTest(CharSource in, CharSink out)366 private static int runSuppressionFailureTest(CharSource in, CharSink out) { 367 try { 368 in.copyTo(out); 369 fail(); 370 } catch (IOException expected) { 371 return CloserTest.getSuppressed(expected).length; 372 } 373 throw new AssertionError(); // can't happen 374 } 375 newNormalCharSource()376 private static CharSource newNormalCharSource() { 377 return CharSource.wrap("ABC"); 378 } 379 newNormalCharSink()380 private static CharSink newNormalCharSink() { 381 return new CharSink() { 382 @Override 383 public Writer openStream() { 384 return new StringWriter(); 385 } 386 }; 387 } 388 } 389