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