1/*
2 * Copyright (C) 2014 Square, Inc.
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 */
16package okio;
17
18import java.io.IOException;
19import java.io.InputStream;
20import java.util.zip.Deflater;
21import java.util.zip.Inflater;
22import java.util.zip.InflaterInputStream;
23import org.junit.Test;
24
25import static okio.TestUtil.randomBytes;
26import static okio.TestUtil.repeat;
27import static org.junit.Assert.assertEquals;
28import static org.junit.Assert.fail;
29
30public final class DeflaterSinkTest {
31  @Test public void deflateWithClose() throws Exception {
32    Buffer data = new Buffer();
33    String original = "They're moving in herds. They do move in herds.";
34    data.writeUtf8(original);
35    Buffer sink = new Buffer();
36    DeflaterSink deflaterSink = new DeflaterSink(sink, new Deflater());
37    deflaterSink.write(data, data.size());
38    deflaterSink.close();
39    Buffer inflated = inflate(sink);
40    assertEquals(original, inflated.readUtf8());
41  }
42
43  @Test public void deflateWithSyncFlush() throws Exception {
44    String original = "Yes, yes, yes. That's why we're taking extreme precautions.";
45    Buffer data = new Buffer();
46    data.writeUtf8(original);
47    Buffer sink = new Buffer();
48    DeflaterSink deflaterSink = new DeflaterSink(sink, new Deflater());
49    deflaterSink.write(data, data.size());
50    deflaterSink.flush();
51    Buffer inflated = inflate(sink);
52    assertEquals(original, inflated.readUtf8());
53  }
54
55  @Test public void deflateWellCompressed() throws IOException {
56    String original = repeat('a', 1024 * 1024);
57    Buffer data = new Buffer();
58    data.writeUtf8(original);
59    Buffer sink = new Buffer();
60    DeflaterSink deflaterSink = new DeflaterSink(sink, new Deflater());
61    deflaterSink.write(data, data.size());
62    deflaterSink.close();
63    Buffer inflated = inflate(sink);
64    assertEquals(original, inflated.readUtf8());
65  }
66
67  @Test public void deflatePoorlyCompressed() throws IOException {
68    ByteString original = randomBytes(1024 * 1024);
69    Buffer data = new Buffer();
70    data.write(original);
71    Buffer sink = new Buffer();
72    DeflaterSink deflaterSink = new DeflaterSink(sink, new Deflater());
73    deflaterSink.write(data, data.size());
74    deflaterSink.close();
75    Buffer inflated = inflate(sink);
76    assertEquals(original, inflated.readByteString());
77  }
78
79  @Test public void multipleSegmentsWithoutCompression() throws IOException {
80    Buffer buffer = new Buffer();
81    Deflater deflater = new Deflater();
82    deflater.setLevel(Deflater.NO_COMPRESSION);
83    DeflaterSink deflaterSink = new DeflaterSink(buffer, deflater);
84    int byteCount = Segment.SIZE * 4;
85    deflaterSink.write(new Buffer().writeUtf8(repeat('a', byteCount)), byteCount);
86    deflaterSink.close();
87    assertEquals(repeat('a', byteCount), inflate(buffer).readUtf8(byteCount));
88  }
89
90  @Test public void deflateIntoNonemptySink() throws Exception {
91    String original = "They're moving in herds. They do move in herds.";
92
93    // Exercise all possible offsets for the outgoing segment.
94    for (int i = 0; i < Segment.SIZE; i++) {
95      Buffer data = new Buffer().writeUtf8(original);
96      Buffer sink = new Buffer().writeUtf8(repeat('a', i));
97
98      DeflaterSink deflaterSink = new DeflaterSink(sink, new Deflater());
99      deflaterSink.write(data, data.size());
100      deflaterSink.close();
101
102      sink.skip(i);
103      Buffer inflated = inflate(sink);
104      assertEquals(original, inflated.readUtf8());
105    }
106  }
107
108  /**
109   * This test deflates a single segment of without compression because that's
110   * the easiest way to force close() to emit a large amount of data to the
111   * underlying sink.
112   */
113  @Test public void closeWithExceptionWhenWritingAndClosing() throws IOException {
114    MockSink mockSink = new MockSink();
115    mockSink.scheduleThrow(0, new IOException("first"));
116    mockSink.scheduleThrow(1, new IOException("second"));
117    Deflater deflater = new Deflater();
118    deflater.setLevel(Deflater.NO_COMPRESSION);
119    DeflaterSink deflaterSink = new DeflaterSink(mockSink, deflater);
120    deflaterSink.write(new Buffer().writeUtf8(repeat('a', Segment.SIZE)), Segment.SIZE);
121    try {
122      deflaterSink.close();
123      fail();
124    } catch (IOException expected) {
125      assertEquals("first", expected.getMessage());
126    }
127    mockSink.assertLogContains("close()");
128  }
129
130  /**
131   * Uses streaming decompression to inflate {@code deflated}. The input must
132   * either be finished or have a trailing sync flush.
133   */
134  private Buffer inflate(Buffer deflated) throws IOException {
135    InputStream deflatedIn = deflated.inputStream();
136    Inflater inflater = new Inflater();
137    InputStream inflatedIn = new InflaterInputStream(deflatedIn, inflater);
138    Buffer result = new Buffer();
139    byte[] buffer = new byte[8192];
140    while (!inflater.needsInput() || deflated.size() > 0 || deflatedIn.available() > 0) {
141      int count = inflatedIn.read(buffer, 0, buffer.length);
142      if (count != -1) {
143        result.write(buffer, 0, count);
144      }
145    }
146    return result;
147  }
148}
149