1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package org.apache.harmony.testframework; 19 20import junit.framework.TestCase; 21import junit.framework.TestSuite; 22 23import java.io.IOException; 24import java.io.Writer; 25 26/** 27 * Tests behaviour common to wrapping and filtering implementations of {@link 28 * Writer}. 29 */ 30public abstract class CharWrapperTester { 31 32 private boolean throwsExceptions = true; 33 34 /** 35 * Creates a new output stream that receives one stream of chars, optionally 36 * transforms it, and emits another stream of chars to {@code delegate}. 37 */ 38 public abstract Writer create(Writer delegate) throws Exception; 39 40 /** 41 * Decodes the chars received by the delegate into their original form: the 42 * chars originally received by this wrapper. 43 */ 44 public abstract char[] decode(char[] delegateChars) throws Exception; 45 46 /** 47 * Configures whether the writer is expected to throw exceptions when an 48 * error is encountered. Classes like {@code PrintWriter} report errors via 49 * an API method instead. 50 */ 51 public CharWrapperTester setThrowsExceptions(boolean throwsExceptions) { 52 this.throwsExceptions = throwsExceptions; 53 return this; 54 } 55 56 public final TestSuite createTests() { 57 TestSuite result = new TestSuite(); 58 result.addTest(new WrapperSinkTester() 59 .setThrowsExceptions(throwsExceptions) 60 .createTests()); 61 62 if (throwsExceptions) { 63 result.addTest(new WrapperTestCase("wrapperTestFlushThrowsViaFlush")); 64 result.addTest(new WrapperTestCase("wrapperTestFlushThrowsViaClose")); 65 result.addTest(new WrapperTestCase("wrapperTestCloseThrows")); 66 } else { 67 result.addTest(new WrapperTestCase("wrapperTestFlushThrowsViaFlushSuppressed")); 68 result.addTest(new WrapperTestCase("wrapperTestFlushThrowsViaCloseSuppressed")); 69 result.addTest(new WrapperTestCase("wrapperTestCloseThrowsSuppressed")); 70 } 71 72 return result; 73 } 74 75 @Override 76 public String toString() { 77 return getClass().getName(); 78 } 79 80 private class WrapperSinkTester extends CharSinkTester { 81 private ClosableStringWriter delegate; 82 83 @Override 84 public Writer create() throws Exception { 85 delegate = new ClosableStringWriter(); 86 return CharWrapperTester.this.create(delegate); 87 } 88 89 @Override 90 public char[] getChars() throws Exception { 91 return decode(delegate.buffer.toString().toCharArray()); 92 } 93 94 @Override 95 public String toString() { 96 return CharWrapperTester.this.toString(); 97 } 98 } 99 100 public class WrapperTestCase extends TestCase { 101 102 private WrapperTestCase(String name) { 103 super(name); 104 } 105 106 @Override 107 public String getName() { 108 return CharWrapperTester.this.toString() + ":" + super.getName(); 109 } 110 111 public void wrapperTestFlushThrowsViaFlushSuppressed() throws Exception { 112 FailOnFlushWriter delegate = new FailOnFlushWriter(); 113 Writer o = create(delegate); 114 o.write("BUT"); 115 o.write("TERS"); 116 o.flush(); 117 assertTrue(delegate.flushed); 118 } 119 120 public void wrapperTestFlushThrowsViaCloseSuppressed() throws Exception { 121 FailOnFlushWriter delegate = new FailOnFlushWriter(); 122 Writer o = create(delegate); 123 o.write("BUT"); 124 o.write("TERS"); 125 o.close(); 126 assertTrue(delegate.flushed); 127 } 128 129 public void wrapperTestFlushThrowsViaFlush() throws Exception { 130 FailOnFlushWriter delegate = new FailOnFlushWriter(); 131 132 Writer o = create(delegate); 133 try { 134 // any of these is permitted to flush 135 o.write("BUT"); 136 o.write("TERS"); 137 o.flush(); 138 assertTrue(delegate.flushed); 139 fail("flush exception ignored"); 140 } catch (IOException expected) { 141 assertEquals("Flush failed", expected.getMessage()); 142 } 143 } 144 145 public void wrapperTestFlushThrowsViaClose() throws Exception { 146 FailOnFlushWriter delegate = new FailOnFlushWriter(); 147 148 Writer o = create(delegate); 149 try { 150 // any of these is permitted to flush 151 o.write("BUT"); 152 o.write("TERS"); 153 o.close(); 154 assertTrue(delegate.flushed); 155 fail("flush exception ignored"); 156 } catch (IOException expected) { 157 assertEquals("Flush failed", expected.getMessage()); 158 } 159 160 try { 161 o.write("BARK"); 162 fail("expected already closed exception"); 163 } catch (IOException expected) { 164 } 165 } 166 167 public void wrapperTestCloseThrows() throws Exception { 168 FailOnCloseWriter delegate = new FailOnCloseWriter(); 169 Writer o = create(delegate); 170 try { 171 o.close(); 172 assertTrue(delegate.closed); 173 fail("close exception ignored"); 174 } catch (IOException expected) { 175 assertEquals("Close failed", expected.getMessage()); 176 } 177 } 178 179 public void wrapperTestCloseThrowsSuppressed() throws Exception { 180 FailOnCloseWriter delegate = new FailOnCloseWriter(); 181 Writer o = create(delegate); 182 o.close(); 183 assertTrue(delegate.closed); 184 } 185 186 // adding a new test? Don't forget to update createTests(). 187 } 188 189 /** 190 * A custom Writer that respects the closed state. The built-in StringWriter 191 * doesn't respect close(), which makes testing wrapped streams difficult. 192 */ 193 private static class ClosableStringWriter extends Writer { 194 private final StringBuilder buffer = new StringBuilder(); 195 private boolean closed = false; 196 197 @Override 198 public void close() throws IOException { 199 closed = true; 200 } 201 202 @Override 203 public void flush() throws IOException { 204 } 205 206 @Override 207 public void write(char[] buf, int offset, int count) throws IOException { 208 if (closed) { 209 throw new IOException(); 210 } 211 buffer.append(buf, offset, count); 212 } 213 } 214 215 private static class FailOnFlushWriter extends Writer { 216 boolean flushed = false; 217 boolean closed = false; 218 219 @Override 220 public void write(char[] buf, int offset, int count) throws IOException { 221 if (closed) { 222 throw new IOException("Already closed"); 223 } 224 } 225 226 @Override 227 public void close() throws IOException { 228 closed = true; 229 flush(); 230 } 231 232 @Override 233 public void flush() throws IOException { 234 if (!flushed) { 235 flushed = true; 236 throw new IOException("Flush failed"); 237 } 238 } 239 } 240 241 private static class FailOnCloseWriter extends Writer { 242 boolean closed = false; 243 244 @Override 245 public void flush() throws IOException { 246 } 247 248 @Override 249 public void write(char[] buf, int offset, int count) throws IOException { 250 } 251 252 @Override 253 public void close() throws IOException { 254 closed = true; 255 throw new IOException("Close failed"); 256 } 257 } 258}