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.EOFException;
19import java.io.IOException;
20import java.io.OutputStream;
21import java.math.BigInteger;
22import java.nio.charset.Charset;
23import java.util.Arrays;
24import java.util.List;
25import org.junit.Before;
26import org.junit.Test;
27import org.junit.runner.RunWith;
28import org.junit.runners.Parameterized;
29
30import static java.util.Arrays.asList;
31import static okio.TestUtil.repeat;
32import static okio.Util.UTF_8;
33import static org.junit.Assert.assertEquals;
34import static org.junit.Assert.fail;
35
36@RunWith(Parameterized.class)
37public class BufferedSinkTest {
38  private interface Factory {
39    BufferedSink create(Buffer data);
40  }
41
42  // ANDROID-BEGIN
43  //  @Parameterized.Parameters(name = "{0}")
44  @Parameterized.Parameters
45  // ANDROID-END
46  public static List<Object[]> parameters() {
47    return Arrays.asList(new Object[] {
48        new Factory() {
49          @Override public BufferedSink create(Buffer data) {
50            return data;
51          }
52
53          @Override public String toString() {
54            return "Buffer";
55          }
56        }
57    }, new Object[] {
58        new Factory() {
59          @Override public BufferedSink create(Buffer data) {
60            return new RealBufferedSink(data);
61          }
62
63          @Override public String toString() {
64            return "RealBufferedSink";
65          }
66        }
67    });
68  }
69
70  // ANDROID-BEGIN
71  //  @Parameterized.Parameter
72  private final Factory factory;
73  public BufferedSinkTest(Factory factory) {
74    this.factory = factory;
75  }
76  // ANDROID-END
77
78  private Buffer data;
79  private BufferedSink sink;
80
81  @Before public void setUp() {
82    data = new Buffer();
83    sink = factory.create(data);
84  }
85
86  @Test public void writeNothing() throws IOException {
87    sink.writeUtf8("");
88    sink.flush();
89    assertEquals(0, data.size());
90  }
91
92  @Test public void writeBytes() throws Exception {
93    sink.writeByte(0xab);
94    sink.writeByte(0xcd);
95    sink.flush();
96    assertEquals("Buffer[size=2 data=abcd]", data.toString());
97  }
98
99  @Test public void writeLastByteInSegment() throws Exception {
100    sink.writeUtf8(repeat('a', Segment.SIZE - 1));
101    sink.writeByte(0x20);
102    sink.writeByte(0x21);
103    sink.flush();
104    assertEquals(asList(Segment.SIZE, 1), data.segmentSizes());
105    assertEquals(repeat('a', Segment.SIZE - 1), data.readUtf8(Segment.SIZE - 1));
106    assertEquals("Buffer[size=2 data=2021]", data.toString());
107  }
108
109  @Test public void writeShort() throws Exception {
110    sink.writeShort(0xabcd);
111    sink.writeShort(0x4321);
112    sink.flush();
113    assertEquals("Buffer[size=4 data=abcd4321]", data.toString());
114  }
115
116  @Test public void writeShortLe() throws Exception {
117    sink.writeShortLe(0xabcd);
118    sink.writeShortLe(0x4321);
119    sink.flush();
120    assertEquals("Buffer[size=4 data=cdab2143]", data.toString());
121  }
122
123  @Test public void writeInt() throws Exception {
124    sink.writeInt(0xabcdef01);
125    sink.writeInt(0x87654321);
126    sink.flush();
127    assertEquals("Buffer[size=8 data=abcdef0187654321]", data.toString());
128  }
129
130  @Test public void writeLastIntegerInSegment() throws Exception {
131    sink.writeUtf8(repeat('a', Segment.SIZE - 4));
132    sink.writeInt(0xabcdef01);
133    sink.writeInt(0x87654321);
134    sink.flush();
135    assertEquals(asList(Segment.SIZE, 4), data.segmentSizes());
136    assertEquals(repeat('a', Segment.SIZE - 4), data.readUtf8(Segment.SIZE - 4));
137    assertEquals("Buffer[size=8 data=abcdef0187654321]", data.toString());
138  }
139
140  @Test public void writeIntegerDoesNotQuiteFitInSegment() throws Exception {
141    sink.writeUtf8(repeat('a', Segment.SIZE - 3));
142    sink.writeInt(0xabcdef01);
143    sink.writeInt(0x87654321);
144    sink.flush();
145    assertEquals(asList(Segment.SIZE - 3, 8), data.segmentSizes());
146    assertEquals(repeat('a', Segment.SIZE - 3), data.readUtf8(Segment.SIZE - 3));
147    assertEquals("Buffer[size=8 data=abcdef0187654321]", data.toString());
148  }
149
150  @Test public void writeIntLe() throws Exception {
151    sink.writeIntLe(0xabcdef01);
152    sink.writeIntLe(0x87654321);
153    sink.flush();
154    assertEquals("Buffer[size=8 data=01efcdab21436587]", data.toString());
155  }
156
157  @Test public void writeLong() throws Exception {
158    sink.writeLong(0xabcdef0187654321L);
159    sink.writeLong(0xcafebabeb0b15c00L);
160    sink.flush();
161    assertEquals("Buffer[size=16 data=abcdef0187654321cafebabeb0b15c00]", data.toString());
162  }
163
164  @Test public void writeLongLe() throws Exception {
165    sink.writeLongLe(0xabcdef0187654321L);
166    sink.writeLongLe(0xcafebabeb0b15c00L);
167    sink.flush();
168    assertEquals("Buffer[size=16 data=2143658701efcdab005cb1b0bebafeca]", data.toString());
169  }
170
171  @Test public void writeStringUtf8() throws IOException {
172    sink.writeUtf8("təˈranəˌsôr");
173    sink.flush();
174    assertEquals(ByteString.decodeHex("74c999cb8872616ec999cb8c73c3b472"), data.readByteString());
175  }
176
177  @Test public void writeSubstringUtf8() throws IOException {
178    sink.writeUtf8("təˈranəˌsôr", 3, 7);
179    sink.flush();
180    assertEquals(ByteString.decodeHex("72616ec999"), data.readByteString());
181  }
182
183  @Test public void writeStringWithCharset() throws IOException {
184    sink.writeString("təˈranəˌsôr", Charset.forName("utf-32be"));
185    sink.flush();
186    assertEquals(ByteString.decodeHex("0000007400000259000002c800000072000000610000006e00000259"
187        + "000002cc00000073000000f400000072"), data.readByteString());
188  }
189
190  @Test public void writeSubstringWithCharset() throws IOException {
191    sink.writeString("təˈranəˌsôr", 3, 7, Charset.forName("utf-32be"));
192    sink.flush();
193    assertEquals(ByteString.decodeHex("00000072000000610000006e00000259"), data.readByteString());
194  }
195
196  @Test public void writeAll() throws Exception {
197    Buffer source = new Buffer().writeUtf8("abcdef");
198
199    assertEquals(6, sink.writeAll(source));
200    assertEquals(0, source.size());
201    sink.flush();
202    assertEquals("abcdef", data.readUtf8());
203  }
204
205  @Test public void writeSource() throws Exception {
206    Buffer source = new Buffer().writeUtf8("abcdef");
207
208    // Force resolution of the Source method overload.
209    sink.write((Source) source, 4);
210    sink.flush();
211    assertEquals("abcd", data.readUtf8());
212    assertEquals("ef", source.readUtf8());
213  }
214
215  @Test public void writeSourceReadsFully() throws Exception {
216    Source source = new ForwardingSource(new Buffer()) {
217      @Override public long read(Buffer sink, long byteCount) throws IOException {
218        sink.writeUtf8("abcd");
219        return 4;
220      }
221    };
222
223    sink.write(source, 8);
224    sink.flush();
225    assertEquals("abcdabcd", data.readUtf8());
226  }
227
228  @Test public void writeSourcePropagatesEof() throws IOException {
229    Source source = new Buffer().writeUtf8("abcd");
230
231    try {
232      sink.write(source, 8);
233      fail();
234    } catch (EOFException expected) {
235    }
236
237    // Ensure that whatever was available was correctly written.
238    sink.flush();
239    assertEquals("abcd", data.readUtf8());
240  }
241
242  @Test public void writeSourceWithZeroIsNoOp() throws IOException {
243    // This test ensures that a zero byte count never calls through to read the source. It may be
244    // tied to something like a socket which will potentially block trying to read a segment when
245    // ultimately we don't want any data.
246    Source source = new ForwardingSource(new Buffer()) {
247      @Override public long read(Buffer sink, long byteCount) throws IOException {
248        throw new AssertionError();
249      }
250    };
251    sink.write(source, 0);
252    assertEquals(0, data.size());
253  }
254
255  @Test public void writeAllExhausted() throws Exception {
256    Buffer source = new Buffer();
257    assertEquals(0, sink.writeAll(source));
258    assertEquals(0, source.size());
259  }
260
261  @Test public void closeEmitsBufferedBytes() throws IOException {
262    sink.writeByte('a');
263    sink.close();
264    assertEquals('a', data.readByte());
265  }
266
267  @Test public void outputStream() throws Exception {
268    OutputStream out = sink.outputStream();
269    out.write('a');
270    out.write(repeat('b', 9998).getBytes(UTF_8));
271    out.write('c');
272    out.flush();
273    assertEquals("a" + repeat('b', 9998) + "c", data.readUtf8());
274  }
275
276  @Test public void outputStreamBounds() throws Exception {
277    OutputStream out = sink.outputStream();
278    try {
279      out.write(new byte[100], 50, 51);
280      fail();
281    } catch (ArrayIndexOutOfBoundsException expected) {
282    }
283  }
284
285  @Test public void longDecimalString() throws IOException {
286    assertLongDecimalString(0);
287    assertLongDecimalString(Long.MIN_VALUE);
288    assertLongDecimalString(Long.MAX_VALUE);
289
290    for (int i = 1; i < 20; i++) {
291      long value = BigInteger.valueOf(10L).pow(i).longValue();
292      assertLongDecimalString(value - 1);
293      assertLongDecimalString(value);
294    }
295  }
296
297  private void assertLongDecimalString(long value) throws IOException {
298    sink.writeDecimalLong(value).writeUtf8("zzz").flush();
299    String expected = Long.toString(value) + "zzz";
300    String actual = data.readUtf8();
301    assertEquals(value + " expected " + expected + " but was " + actual, actual, expected);
302  }
303
304  @Test public void longHexString() throws IOException {
305    assertLongHexString(0);
306    assertLongHexString(Long.MIN_VALUE);
307    assertLongHexString(Long.MAX_VALUE);
308
309    for (int i = 0; i < 16; i++) {
310      assertLongHexString((1 << i) - 1);
311      assertLongHexString(1 << i);
312    }
313  }
314
315  private void assertLongHexString(long value) throws IOException {
316    sink.writeHexadecimalUnsignedLong(value).writeUtf8("zzz").flush();
317    String expected = String.format("%x", value) + "zzz";
318    String actual = data.readUtf8();
319    assertEquals(value + " expected " + expected + " but was " + actual, actual, expected);
320  }
321}
322