1/*
2 * Copyright (C) 2010 The Android Open Source Project
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
17package libcore.java.util.zip;
18
19import java.io.ByteArrayInputStream;
20import java.io.ByteArrayOutputStream;
21import java.io.EOFException;
22import java.io.InputStream;
23import java.io.IOException;
24import java.io.OutputStream;
25import java.io.PipedInputStream;
26import java.io.PipedOutputStream;
27import java.lang.reflect.Field;
28import java.util.Arrays;
29import java.util.concurrent.Callable;
30import java.util.concurrent.ExecutorService;
31import java.util.concurrent.Executors;
32import java.util.zip.Deflater;
33import java.util.zip.DeflaterOutputStream;
34import java.util.zip.GZIPInputStream;
35import java.util.zip.GZIPOutputStream;
36import java.util.zip.InflaterInputStream;
37import junit.framework.TestCase;
38
39public class DeflaterOutputStreamTest extends TestCase {
40
41    public void testSyncFlushEnabled() throws Exception {
42        InputStream in = createInflaterStream(DeflaterOutputStream.class, true);
43        assertEquals(1, in.read());
44        assertEquals(2, in.read());
45        assertEquals(3, in.read());
46        in.close();
47    }
48
49    public void testSyncFlushDisabled() throws Exception {
50        InputStream in = createInflaterStream(DeflaterOutputStream.class, false);
51        try {
52            in.read();
53            fail();
54        } catch (IOException expected) {
55        }
56        in.close();
57    }
58
59    /**
60     * Creates an optionally-flushing deflater stream, writes some bytes to it,
61     * and flushes it. Returns an inflater stream that reads this deflater's
62     * output.
63     *
64     * <p>These bytes are written on a separate thread so that when the inflater
65     * stream is read, that read will fail when no bytes are available. Failing
66     * takes 3 seconds, co-ordinated by PipedInputStream's 'broken pipe'
67     * timeout. The 3 second delay is unfortunate but seems to be the easiest
68     * way demonstrate that data is unavailable. Ie. other techniques will cause
69     * the dry read to block indefinitely.
70     */
71    static InputStream createInflaterStream(final Class<?> c, final boolean flushing) throws Exception {
72        ExecutorService executor = Executors.newSingleThreadExecutor();
73        final PipedOutputStream pout = new PipedOutputStream();
74        PipedInputStream pin = new PipedInputStream(pout);
75
76        executor.submit(new Callable<Void>() {
77            public Void call() throws Exception {
78                OutputStream out;
79                if (c == DeflaterOutputStream.class) {
80                    out = new DeflaterOutputStream(pout, flushing);
81                } else if (c == GZIPOutputStream.class) {
82                    out = new GZIPOutputStream(pout, flushing);
83                } else {
84                    throw new AssertionError();
85                }
86                out.write(1);
87                out.write(2);
88                out.write(3);
89                out.flush();
90                return null;
91            }
92        }).get();
93        executor.shutdown();
94
95        if (c == DeflaterOutputStream.class) {
96            return new InflaterInputStream(pin);
97        } else if (c == GZIPOutputStream.class) {
98            return new GZIPInputStream(pin);
99        } else {
100            throw new AssertionError();
101        }
102    }
103
104    static class FlushingDeflater extends Deflater {
105        @Override
106        public int deflate(byte[] buf, int offset, int byteCount) {
107            return super.deflate(buf, offset, byteCount, Deflater.SYNC_FLUSH);
108        }
109    }
110
111    /**
112     * Confirm that a DeflaterOutputStream constructed with Deflater
113     * with flushParm == SYNC_FLUSH does not need to to be flushed.
114     *
115     * http://b/4005091
116     */
117    public void testSyncFlushDeflater() throws Exception {
118        Deflater def = new FlushingDeflater();
119        final int deflaterBufferSize = 512;
120        ByteArrayOutputStream baos = new ByteArrayOutputStream();
121        DeflaterOutputStream dos = new DeflaterOutputStream(baos, def, deflaterBufferSize);
122
123        // make output buffer large enough that even if compressed it
124        // won't all fit within the deflaterBufferSize.
125        final int outputBufferSize = 128 * deflaterBufferSize;
126        byte[] output = new byte[outputBufferSize];
127        for (int i = 0; i < output.length; i++) {
128            output[i] = (byte) i;
129        }
130        dos.write(output);
131        byte[] compressed = baos.toByteArray();
132        // this main reason for this assert is to make sure that the
133        // compressed byte count is larger than the
134        // deflaterBufferSize. However, when the original bug exists,
135        // it will also fail because the compressed length will be
136        // exactly the length of the deflaterBufferSize.
137        assertTrue("compressed=" + compressed.length
138                   + " but deflaterBufferSize=" + deflaterBufferSize,
139                   compressed.length > deflaterBufferSize);
140
141        // assert that we returned data matches the input exactly.
142        ByteArrayInputStream bais = new ByteArrayInputStream(compressed);
143        InflaterInputStream iis = new InflaterInputStream(bais);
144        byte[] input = new byte[output.length];
145        int total = 0;
146        while (true)  {
147            int n = iis.read(input, total, input.length - total);
148            if (n == -1) {
149                break;
150            }
151            total += n;
152            if (total == input.length) {
153                try {
154                    iis.read();
155                    fail();
156                } catch (EOFException expected) {
157                    break;
158                }
159            }
160        }
161        assertEquals(output.length, total);
162        assertTrue(Arrays.equals(input, output));
163
164        // ensure Deflater.finish has not been called at any point
165        // during the test, since that would lead to the results being
166        // flushed even without SYNC_FLUSH being used
167        assertFalse(def.finished());
168
169        // Quieten CloseGuard.
170        def.end();
171        iis.close();
172    }
173}
174