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