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