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