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.ByteArrayOutputStream;
24import java.io.IOException;
25import java.io.OutputStream;
26
27/**
28 * Tests behaviour common to wrapping and filtering implementations of {@link
29 * OutputStream}.
30 */
31public abstract class WrapperTester {
32
33    private boolean throwsExceptions = true;
34
35    /**
36     * Creates a new output stream that receives one stream of bytes, optionally
37     * transforms it, and emits another stream of bytes to {@code delegate}.
38     */
39    public abstract OutputStream create(OutputStream delegate) throws Exception;
40
41    /**
42     * Decodes the bytes received by the delegate into their original form: the
43     * bytes originally received by this wrapper.
44     */
45    public abstract byte[] decode(byte[] delegateBytes) throws Exception;
46
47    /**
48     * Configures whether the stream is expected to throw exceptions when an
49     * error is encountered. Classes like {@code PrintStream} report errors via
50     * an API method instead.
51     */
52    public WrapperTester setThrowsExceptions(boolean throwsExceptions) {
53        this.throwsExceptions = throwsExceptions;
54        return this;
55    }
56
57    public final TestSuite createTests() {
58        TestSuite result = new TestSuite();
59        result.addTest(new WrapperSinkTester()
60                .setThrowsExceptions(throwsExceptions)
61                .createTests());
62
63        if (throwsExceptions) {
64            result.addTest(new WrapperTestCase("wrapperTestFlushThrowsViaFlush"));
65            result.addTest(new WrapperTestCase("wrapperTestFlushThrowsViaClose"));
66            result.addTest(new WrapperTestCase("wrapperTestCloseThrows"));
67        } else {
68            result.addTest(new WrapperTestCase("wrapperTestFlushThrowsViaFlushSuppressed"));
69            result.addTest(new WrapperTestCase("wrapperTestFlushThrowsViaCloseSuppressed"));
70            result.addTest(new WrapperTestCase("wrapperTestCloseThrowsSuppressed"));
71        }
72
73        return result;
74    }
75
76    @Override public String toString() {
77        return getClass().getName();
78    }
79
80    private class WrapperSinkTester extends SinkTester {
81        private ClosableByteArrayOutputStream delegate;
82
83        @Override public OutputStream create() throws Exception {
84            delegate = new ClosableByteArrayOutputStream();
85            return WrapperTester.this.create(delegate);
86        }
87
88        @Override public byte[] getBytes() throws Exception {
89            return WrapperTester.this.decode(delegate.bytesOut.toByteArray());
90        }
91
92        @Override public String toString() {
93            return WrapperTester.this.toString();
94        }
95    }
96
97    public class WrapperTestCase extends TestCase {
98
99        private WrapperTestCase(String name) {
100            super(name);
101        }
102
103        @Override public String getName() {
104            return WrapperTester.this.toString() + ":" + super.getName();
105        }
106
107        public void wrapperTestFlushThrowsViaFlushSuppressed() throws Exception {
108            FailOnFlushOutputStream delegate = new FailOnFlushOutputStream();
109            OutputStream o = create(delegate);
110            o.write(new byte[] { 8, 6, 7, 5 });
111            o.write(new byte[] { 3, 0, 9 });
112            o.flush();
113            assertTrue(delegate.flushed);
114        }
115
116        public void wrapperTestFlushThrowsViaCloseSuppressed() throws Exception {
117            FailOnFlushOutputStream delegate = new FailOnFlushOutputStream();
118            OutputStream o = create(delegate);
119            o.write(new byte[] { 8, 6, 7, 5 });
120            o.write(new byte[] { 3, 0, 9 });
121            o.close();
122            assertTrue(delegate.flushed);
123        }
124
125        public void wrapperTestFlushThrowsViaFlush() throws Exception {
126            FailOnFlushOutputStream delegate = new FailOnFlushOutputStream();
127
128            OutputStream o = create(delegate);
129            try {
130                // any of these is permitted to flush
131                o.write(new byte[] { 8, 6, 7, 5 });
132                o.write(new byte[] { 3, 0, 9 });
133                o.flush();
134                assertTrue(delegate.flushed);
135                fail("flush exception ignored");
136            } catch (IOException expected) {
137                assertEquals("Flush failed" , expected.getMessage());
138            }
139        }
140
141        public void wrapperTestFlushThrowsViaClose() throws Exception {
142            FailOnFlushOutputStream delegate = new FailOnFlushOutputStream();
143
144            OutputStream o = create(delegate);
145            try {
146                // any of these is permitted to flush
147                o.write(new byte[] { 8, 6, 7, 5 });
148                o.write(new byte[] { 3, 0, 9 });
149                o.close();
150                assertTrue(delegate.flushed);
151                fail("flush exception ignored");
152            } catch (IOException expected) {
153                assertEquals("Flush failed" , expected.getMessage());
154            }
155
156            try {
157                o.write(new byte[] { 4, 4, 5 });
158                fail("expected already closed exception");
159            } catch (IOException expected) {
160            }
161        }
162
163        public void wrapperTestCloseThrows() throws Exception {
164            FailOnCloseOutputStream delegate = new FailOnCloseOutputStream();
165            OutputStream o = create(delegate);
166            try {
167                o.close();
168                assertTrue(delegate.closed);
169                fail("close exception ignored");
170            } catch (IOException expected) {
171                assertEquals("Close failed" , expected.getMessage());
172            }
173        }
174
175        public void wrapperTestCloseThrowsSuppressed() throws Exception {
176            FailOnCloseOutputStream delegate = new FailOnCloseOutputStream();
177            OutputStream o = create(delegate);
178            o.close();
179            assertTrue(delegate.closed);
180        }
181
182        // adding a new test? Don't forget to update createTests().
183    }
184
185    private static class ClosableByteArrayOutputStream extends OutputStream {
186        private final ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
187        private boolean closed = false;
188
189        @Override public void close() throws IOException {
190            closed = true;
191        }
192
193        @Override public void write(int oneByte) throws IOException {
194            if (closed) {
195                throw new IOException();
196            }
197            bytesOut.write(oneByte);
198        }
199    }
200
201    private static class FailOnFlushOutputStream extends OutputStream {
202        boolean flushed = false;
203        boolean closed = false;
204
205        @Override public void write(int oneByte) throws IOException {
206            if (closed) {
207                throw new IOException("Already closed");
208            }
209        }
210
211        @Override public void close() throws IOException {
212            closed = true;
213            flush();
214        }
215
216        @Override public void flush() throws IOException {
217            if (!flushed) {
218                flushed = true;
219                throw new IOException("Flush failed");
220            }
221        }
222    }
223
224    private static class FailOnCloseOutputStream extends ByteArrayOutputStream {
225        boolean closed = false;
226
227        @Override public void close() throws IOException {
228            closed = true;
229            throw new IOException("Close failed");
230        }
231    }
232}
233