1 /* 2 * Copyright 2009 Google, Inc. All Rights Reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @bug 4206909 4813885 8191918 27 * @summary Test basic functionality of DeflaterOutputStream/InflaterInputStream 28 * and GZIPOutputStream/GZIPInputStream, including flush 29 * @key randomness 30 */ 31 32 import java.io.*; 33 import java.util.*; 34 import java.util.zip.*; 35 36 public class InflateIn_DeflateOut { 37 38 private static class PairedInputStream extends ByteArrayInputStream { 39 private PairedOutputStream out = null; 40 private Random random; 41 PairedInputStream()42 public PairedInputStream() { 43 // The ByteArrayInputStream needs to start with a buffer, but we 44 // need to set it to have no data 45 super(new byte[1]); 46 count = 0; 47 pos = 0; 48 random = new Random(new Date().getTime()); 49 } 50 setPairedOutputStream(PairedOutputStream out)51 public void setPairedOutputStream(PairedOutputStream out) { 52 this.out = out; 53 } 54 maybeFlushPair()55 private void maybeFlushPair() { 56 if (random.nextInt(100) < 10) { 57 out.flush(); 58 } 59 } 60 read()61 public int read() { 62 maybeFlushPair(); 63 return super.read(); 64 } 65 read(byte b[], int off, int len)66 public int read(byte b[], int off, int len) { 67 maybeFlushPair(); 68 return super.read(b, off, len); 69 } 70 addBytes(byte[] bytes, int len)71 public void addBytes(byte[] bytes, int len) { 72 int oldavail = count - pos; 73 int newcount = oldavail + len; 74 byte[] newbuf = new byte[newcount]; 75 System.arraycopy(buf, pos, newbuf, 0, oldavail); 76 System.arraycopy(bytes, 0, newbuf, oldavail, len); 77 pos = 0; 78 count = newcount; 79 buf = newbuf; 80 } 81 } 82 83 private static class PairedOutputStream extends ByteArrayOutputStream { 84 private PairedInputStream pairedStream = null; 85 PairedOutputStream(PairedInputStream inputPair)86 public PairedOutputStream(PairedInputStream inputPair) { 87 super(); 88 this.pairedStream = inputPair; 89 } 90 flush()91 public void flush() { 92 if (count > 0) { 93 pairedStream.addBytes(buf, count); 94 reset(); 95 } 96 } 97 close()98 public void close() { 99 flush(); 100 } 101 } 102 readFully(InputStream in, byte[] buf, int length)103 private static boolean readFully(InputStream in, byte[] buf, int length) 104 throws IOException { 105 int pos = 0; 106 int n; 107 while ((n = in.read(buf, pos, length - pos)) > 0) { 108 pos += n; 109 if (pos == length) return true; 110 } 111 return false; 112 } 113 readLineIfAvailable(InputStream in, StringBuilder sb)114 private static boolean readLineIfAvailable(InputStream in, StringBuilder sb) 115 throws IOException { 116 try { 117 while (in.available() > 0) { 118 int i = in.read(); 119 if (i < 0) break; 120 char c = (char) (((byte) i) & 0xff); 121 sb.append(c); 122 if (c == '\n') return true; 123 } 124 } catch (EOFException e) { 125 // empty 126 } 127 return false; 128 } 129 130 /** Check that written, closed and read */ WriteCloseRead()131 private static void WriteCloseRead() throws Throwable { 132 Random random = new Random(new Date().getTime()); 133 134 PairedInputStream pis = new PairedInputStream(); 135 InflaterInputStream iis = new InflaterInputStream(pis); 136 137 PairedOutputStream pos = new PairedOutputStream(pis); 138 pis.setPairedOutputStream(pos); 139 140 byte[] data = new byte[random.nextInt(1024 * 1024)]; 141 byte[] buf = new byte[data.length]; 142 random.nextBytes(data); 143 144 try (DeflaterOutputStream dos = new DeflaterOutputStream(pos, true)) { 145 dos.write(data); 146 } 147 check(readFully(iis, buf, buf.length)); 148 check(Arrays.equals(data, buf)); 149 } 150 TestFlushableGZIPOutputStream()151 private static void TestFlushableGZIPOutputStream() throws Throwable { 152 var random = new Random(new Date().getTime()); 153 154 var byteOutStream = new ByteArrayOutputStream(); 155 var output = new FlushableGZIPOutputStream(byteOutStream); 156 157 var data = new byte[random.nextInt(1024 * 1024)]; 158 var buf = new byte[data.length]; 159 random.nextBytes(data); 160 161 output.write(data); 162 for (int i=0; i<data.length; i++) { 163 output.write(data[i]); 164 } 165 output.flush(); 166 for (int i=0; i<data.length; i++) { 167 output.write(data[i]); 168 } 169 output.write(data); 170 output.close(); 171 172 var baos = new ByteArrayOutputStream(); 173 try (var gzis = new GZIPInputStream(new 174 ByteArrayInputStream(byteOutStream.toByteArray()))) { 175 gzis.transferTo(baos); 176 } 177 var decompressedBytes = baos.toByteArray(); 178 check(decompressedBytes.length == data.length * 4); 179 } 180 check(InputStream is, OutputStream os)181 private static void check(InputStream is, OutputStream os) 182 throws Throwable 183 { 184 Random random = new Random(new Date().getTime()); 185 // Large writes 186 for (int x = 0; x < 200 ; x++) { 187 // byte[] data = new byte[random.nextInt(1024 * 1024)]; 188 byte[] data = new byte[1024]; 189 byte[] buf = new byte[data.length]; 190 random.nextBytes(data); 191 192 os.write(data); 193 os.flush(); 194 check(readFully(is, buf, buf.length)); 195 check(Arrays.equals(data, buf)); 196 } 197 198 // Small writes 199 for (int x = 0; x < 2000 ; x++) { 200 byte[] data = new byte[random.nextInt(20) + 10]; 201 byte[] buf = new byte[data.length]; 202 random.nextBytes(data); 203 204 os.write(data); 205 os.flush(); 206 if (!readFully(is, buf, buf.length)) { 207 fail("Didn't read full buffer of " + buf.length); 208 } 209 check(Arrays.equals(data, buf)); 210 } 211 212 String quit = "QUIT\r\n"; 213 214 // Close it out 215 os.write(quit.getBytes()); 216 os.close(); 217 218 StringBuilder sb = new StringBuilder(); 219 check(readLineIfAvailable(is, sb)); 220 equal(sb.toString(), quit); 221 } 222 223 /** Check that written, flushed and read */ WriteFlushRead()224 private static void WriteFlushRead() throws Throwable { 225 PairedInputStream pis = new PairedInputStream(); 226 InflaterInputStream iis = new InflaterInputStream(pis); 227 228 PairedOutputStream pos = new PairedOutputStream(pis); 229 pis.setPairedOutputStream(pos); 230 DeflaterOutputStream dos = new DeflaterOutputStream(pos, true); 231 232 check(iis, dos); 233 } 234 GZWriteFlushRead()235 private static void GZWriteFlushRead() throws Throwable { 236 PairedInputStream pis = new PairedInputStream(); 237 PairedOutputStream pos = new PairedOutputStream(pis); 238 pis.setPairedOutputStream(pos); 239 240 GZIPOutputStream gos = new GZIPOutputStream(pos, true); 241 gos.flush(); // flush the head out, so gis can read 242 GZIPInputStream gis = new GZIPInputStream(pis); 243 244 check(gis, gos); 245 } 246 checkLOP(InputStream is, OutputStream os)247 private static void checkLOP(InputStream is, OutputStream os) 248 throws Throwable 249 { 250 boolean flushed = false; 251 int count = 0; 252 253 // Do at least a certain number of lines, but too many without a 254 // flush means this test isn't testing anything 255 while ((count < 10 && flushed) || (count < 1000 && !flushed)) { 256 String command = "PING " + count + "\r\n"; 257 os.write(command.getBytes()); 258 259 StringBuilder buf = new StringBuilder(); 260 if (!readLineIfAvailable(is, buf)) { 261 flushed = true; 262 os.flush(); 263 check(readLineIfAvailable(is, buf)); 264 } 265 equal(buf.toString(), command); 266 count++; 267 } 268 check(flushed); 269 } 270 271 /** Validate that we need to use flush at least once on a line 272 * oriented protocol */ LineOrientedProtocol()273 private static void LineOrientedProtocol() throws Throwable { 274 PairedInputStream pis = new PairedInputStream(); 275 InflaterInputStream iis = new InflaterInputStream(pis); 276 277 PairedOutputStream pos = new PairedOutputStream(pis); 278 pis.setPairedOutputStream(pos); 279 DeflaterOutputStream dos = new DeflaterOutputStream(pos, true); 280 281 checkLOP(iis, dos); 282 } 283 GZLineOrientedProtocol()284 private static void GZLineOrientedProtocol() throws Throwable { 285 PairedInputStream pis = new PairedInputStream(); 286 PairedOutputStream pos = new PairedOutputStream(pis); 287 pis.setPairedOutputStream(pos); 288 289 GZIPOutputStream gos = new GZIPOutputStream(pos, true); 290 gos.flush(); // flush the head out, so gis can read 291 GZIPInputStream gis = new GZIPInputStream(pis); 292 293 checkLOP(gis, gos); 294 } 295 realMain(String[] args)296 public static void realMain(String[] args) throws Throwable { 297 WriteCloseRead(); 298 WriteFlushRead(); 299 LineOrientedProtocol(); 300 GZWriteFlushRead(); 301 GZLineOrientedProtocol(); 302 TestFlushableGZIPOutputStream(); 303 } 304 305 //--------------------- Infrastructure --------------------------- 306 static volatile int passed = 0, failed = 0; pass()307 static void pass() {passed++;} fail()308 static void fail() {failed++; Thread.dumpStack();} fail(String msg)309 static void fail(String msg) {System.out.println(msg); fail();} unexpected(Throwable t)310 static void unexpected(Throwable t) {failed++; t.printStackTrace();} check(boolean cond)311 static void check(boolean cond) {if (cond) pass(); else fail();} equal(Object x, Object y)312 static void equal(Object x, Object y) { 313 if (x == null ? y == null : x.equals(y)) pass(); 314 else fail(x + " not equal to " + y);} main(String[] args)315 public static void main(String[] args) throws Throwable { 316 try {realMain(args);} catch (Throwable t) {unexpected(t);} 317 System.out.println("\nPassed = " + passed + " failed = " + failed); 318 if (failed > 0) throw new AssertionError("Some tests failed");} 319 } 320 321 class FlushableGZIPOutputStream extends GZIPOutputStream { FlushableGZIPOutputStream(OutputStream os)322 public FlushableGZIPOutputStream(OutputStream os) throws IOException { 323 super(os); 324 } 325 326 private static final byte[] EMPTYBYTEARRAY = new byte[0]; 327 private boolean hasData = false; 328 329 /** 330 * Here we make sure we have received data, so that the header has been for 331 * sure written to the output stream already. 332 */ 333 @Override write(byte[] bytes, int i, int i1)334 public synchronized void write(byte[] bytes, int i, int i1) 335 throws IOException { 336 super.write(bytes, i, i1); 337 hasData = true; 338 } 339 340 @Override write(int i)341 public synchronized void write(int i) throws IOException { 342 super.write(i); 343 hasData = true; 344 } 345 346 @Override write(byte[] bytes)347 public synchronized void write(byte[] bytes) throws IOException { 348 super.write(bytes); 349 hasData = true; 350 } 351 352 @Override flush()353 public synchronized void flush() throws IOException { 354 if (!hasData) { 355 return; // do not allow the gzip header to be flushed on its own 356 } 357 358 // trick the deflater to flush 359 /** 360 * Now this is tricky: We force the Deflater to flush its data by 361 * switching compression level. As yet, a perplexingly simple workaround 362 * for 363 * http://developer.java.sun.com/developer/bugParade/bugs/4255743.html 364 */ 365 if (!def.finished()) { 366 def.setInput(EMPTYBYTEARRAY, 0, 0); 367 368 def.setLevel(Deflater.NO_COMPRESSION); 369 deflate(); 370 371 def.setLevel(Deflater.DEFAULT_COMPRESSION); 372 deflate(); 373 374 out.flush(); 375 } 376 377 hasData = false; // no more data to flush 378 } 379 380 /* 381 * Keep on calling deflate until it runs dry. The default implementation 382 * only does it once and can therefore hold onto data when they need to be 383 * flushed out. 384 */ 385 @Override deflate()386 protected void deflate() throws IOException { 387 int len; 388 do { 389 len = def.deflate(buf, 0, buf.length); 390 if (len > 0) { 391 out.write(buf, 0, len); 392 } 393 } while (len != 0); 394 } 395 396 } 397