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 public String toString() { 76 return getClass().getName(); 77 } 78 79 private class WrapperSinkTester extends CharSinkTester { 80 private ClosableStringWriter delegate; 81 82 @Override public Writer create() throws Exception { 83 delegate = new ClosableStringWriter(); 84 return CharWrapperTester.this.create(delegate); 85 } 86 87 @Override public char[] getChars() throws Exception { 88 return decode(delegate.buffer.toString().toCharArray()); 89 } 90 91 @Override public String toString() { 92 return CharWrapperTester.this.toString(); 93 } 94 } 95 96 public class WrapperTestCase extends TestCase { 97 98 private WrapperTestCase(String name) { 99 super(name); 100 } 101 102 @Override public String getName() { 103 return CharWrapperTester.this.toString() + ":" + super.getName(); 104 } 105 106 public void wrapperTestFlushThrowsViaFlushSuppressed() throws Exception { 107 FailOnFlushWriter delegate = new FailOnFlushWriter(); 108 Writer o = create(delegate); 109 o.write("BUT"); 110 o.write("TERS"); 111 o.flush(); 112 assertTrue(delegate.flushed); 113 } 114 115 public void wrapperTestFlushThrowsViaCloseSuppressed() throws Exception { 116 FailOnFlushWriter delegate = new FailOnFlushWriter(); 117 Writer o = create(delegate); 118 o.write("BUT"); 119 o.write("TERS"); 120 o.close(); 121 assertTrue(delegate.flushed); 122 } 123 124 public void wrapperTestFlushThrowsViaFlush() throws Exception { 125 FailOnFlushWriter delegate = new FailOnFlushWriter(); 126 127 Writer o = create(delegate); 128 try { 129 // any of these is permitted to flush 130 o.write("BUT"); 131 o.write("TERS"); 132 o.flush(); 133 assertTrue(delegate.flushed); 134 fail("flush exception ignored"); 135 } catch (IOException expected) { 136 assertEquals("Flush failed" , expected.getMessage()); 137 } 138 } 139 140 public void wrapperTestFlushThrowsViaClose() throws Exception { 141 FailOnFlushWriter delegate = new FailOnFlushWriter(); 142 143 Writer o = create(delegate); 144 try { 145 // any of these is permitted to flush 146 o.write("BUT"); 147 o.write("TERS"); 148 o.close(); 149 assertTrue(delegate.flushed); 150 fail("flush exception ignored"); 151 } catch (IOException expected) { 152 assertEquals("Flush failed" , expected.getMessage()); 153 } 154 155 try { 156 o.write("BARK"); 157 fail("expected already closed exception"); 158 } catch (IOException expected) { 159 } 160 } 161 162 public void wrapperTestCloseThrows() throws Exception { 163 FailOnCloseWriter delegate = new FailOnCloseWriter(); 164 Writer o = create(delegate); 165 try { 166 o.close(); 167 assertTrue(delegate.closed); 168 fail("close exception ignored"); 169 } catch (IOException expected) { 170 assertEquals("Close failed" , expected.getMessage()); 171 } 172 } 173 174 public void wrapperTestCloseThrowsSuppressed() throws Exception { 175 FailOnCloseWriter delegate = new FailOnCloseWriter(); 176 Writer o = create(delegate); 177 o.close(); 178 assertTrue(delegate.closed); 179 } 180 181 // adding a new test? Don't forget to update createTests(). 182 } 183 184 /** 185 * A custom Writer that respects the closed state. The built-in StringWriter 186 * doesn't respect close(), which makes testing wrapped streams difficult. 187 */ 188 private static class ClosableStringWriter extends Writer { 189 private final StringBuilder buffer = new StringBuilder(); 190 private boolean closed = false; 191 192 @Override public void close() throws IOException { 193 closed = true; 194 } 195 196 @Override public void flush() throws IOException {} 197 198 @Override public void write(char[] buf, int offset, int count) throws IOException { 199 if (closed) { 200 throw new IOException(); 201 } 202 buffer.append(buf, offset, count); 203 } 204 } 205 206 private static class FailOnFlushWriter extends Writer { 207 boolean flushed = false; 208 boolean closed = false; 209 210 @Override public void write(char[] buf, int offset, int count) throws IOException { 211 if (closed) { 212 throw new IOException("Already closed"); 213 } 214 } 215 216 @Override public void close() throws IOException { 217 closed = true; 218 flush(); 219 } 220 221 @Override public void flush() throws IOException { 222 if (!flushed) { 223 flushed = true; 224 throw new IOException("Flush failed"); 225 } 226 } 227 } 228 229 private static class FailOnCloseWriter extends Writer { 230 boolean closed = false; 231 232 @Override public void flush() throws IOException {} 233 234 @Override public void write(char[] buf, int offset, int count) throws IOException {} 235 236 @Override public void close() throws IOException { 237 closed = true; 238 throw new IOException("Close failed"); 239 } 240 } 241}