1 // Written in the D programming language. 2 3 /** 4 * Compress/decompress data using the $(HTTP www.zlib.net, zlib library). 5 * 6 * Examples: 7 * 8 * If you have a small buffer you can use $(LREF compress) and 9 * $(LREF uncompress) directly. 10 * 11 * ------- 12 * import std.zlib; 13 * 14 * auto src = 15 * "the quick brown fox jumps over the lazy dog\r 16 * the quick brown fox jumps over the lazy dog\r"; 17 * 18 * ubyte[] dst; 19 * ubyte[] result; 20 * 21 * dst = compress(src); 22 * result = cast(ubyte[]) uncompress(dst); 23 * assert(result == src); 24 * ------- 25 * 26 * When the data to be compressed doesn't fit in one buffer, use 27 * $(LREF Compress) and $(LREF UnCompress). 28 * 29 * ------- 30 * import std.zlib; 31 * import std.stdio; 32 * import std.conv : to; 33 * import std.algorithm.iteration : map; 34 * 35 * UnCompress decmp = new UnCompress; 36 * foreach (chunk; stdin.byChunk(4096).map!(x => decmp.uncompress(x))) 37 * { 38 * chunk.to!string.write; 39 * } 40 41 * ------- 42 * 43 * References: 44 * $(HTTP en.wikipedia.org/wiki/Zlib, Wikipedia) 45 * 46 * Copyright: Copyright The D Language Foundation 2000 - 2011. 47 * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 48 * Authors: $(HTTP digitalmars.com, Walter Bright) 49 * Source: $(PHOBOSSRC std/zlib.d) 50 */ 51 /* Copyright The D Language Foundation 2000 - 2011. 52 * Distributed under the Boost Software License, Version 1.0. 53 * (See accompanying file LICENSE_1_0.txt or copy at 54 * http://www.boost.org/LICENSE_1_0.txt) 55 */ 56 module std.zlib; 57 58 //debug=zlib; // uncomment to turn on debugging printf's 59 60 import etc.c.zlib; 61 62 // Values for 'mode' 63 64 enum 65 { 66 Z_NO_FLUSH = 0, 67 Z_SYNC_FLUSH = 2, 68 Z_FULL_FLUSH = 3, 69 Z_FINISH = 4, 70 } 71 72 /************************************* 73 * Errors throw a ZlibException. 74 */ 75 76 class ZlibException : Exception 77 { 78 private static string getmsg(int errnum) nothrow @nogc pure @safe 79 { 80 string msg; 81 switch (errnum) 82 { 83 case Z_STREAM_END: msg = "stream end"; break; 84 case Z_NEED_DICT: msg = "need dict"; break; 85 case Z_ERRNO: msg = "errno"; break; 86 case Z_STREAM_ERROR: msg = "stream error"; break; 87 case Z_DATA_ERROR: msg = "data error"; break; 88 case Z_MEM_ERROR: msg = "mem error"; break; 89 case Z_BUF_ERROR: msg = "buf error"; break; 90 case Z_VERSION_ERROR: msg = "version error"; break; 91 default: msg = "unknown error"; break; 92 } 93 return msg; 94 } 95 96 this(int errnum) 97 { 98 super(getmsg(errnum)); 99 } 100 } 101 102 /** 103 * $(P Compute the Adler-32 checksum of a buffer's worth of data.) 104 * 105 * Params: 106 * adler = the starting checksum for the computation. Use 1 107 * for a new checksum. Use the output of this function 108 * for a cumulative checksum. 109 * buf = buffer containing input data 110 * 111 * Returns: 112 * A `uint` checksum for the provided input data and starting checksum 113 * 114 * See_Also: 115 * $(LINK http://en.wikipedia.org/wiki/Adler-32) 116 */ 117 118 uint adler32(uint adler, const(void)[] buf) 119 { 120 import std.range : chunks; 121 foreach (chunk; (cast(ubyte[]) buf).chunks(0xFFFF0000)) 122 { 123 adler = etc.c.zlib.adler32(adler, chunk.ptr, cast(uint) chunk.length); 124 } 125 return adler; 126 } 127 128 /// 129 @system unittest 130 { 131 static ubyte[] data = [1,2,3,4,5,6,7,8,9,10]; 132 133 uint adler = adler32(0u, data); 134 assert(adler == 0xdc0037); 135 } 136 137 @system unittest 138 { 139 static string data = "test"; 140 141 uint adler = adler32(1, data); 142 assert(adler == 0x045d01c1); 143 } 144 145 /** 146 * $(P Compute the CRC32 checksum of a buffer's worth of data.) 147 * 148 * Params: 149 * crc = the starting checksum for the computation. Use 0 150 * for a new checksum. Use the output of this function 151 * for a cumulative checksum. 152 * buf = buffer containing input data 153 * 154 * Returns: 155 * A `uint` checksum for the provided input data and starting checksum 156 * 157 * See_Also: 158 * $(LINK http://en.wikipedia.org/wiki/Cyclic_redundancy_check) 159 */ 160 161 uint crc32(uint crc, const(void)[] buf) 162 { 163 import std.range : chunks; 164 foreach (chunk; (cast(ubyte[]) buf).chunks(0xFFFF0000)) 165 { 166 crc = etc.c.zlib.crc32(crc, chunk.ptr, cast(uint) chunk.length); 167 } 168 return crc; 169 } 170 171 @system unittest 172 { 173 static ubyte[] data = [1,2,3,4,5,6,7,8,9,10]; 174 175 uint crc; 176 177 debug(zlib) printf("D.zlib.crc32.unittest\n"); 178 crc = crc32(0u, cast(void[]) data); 179 debug(zlib) printf("crc = %x\n", crc); 180 assert(crc == 0x2520577b); 181 } 182 183 /** 184 * $(P Compress data) 185 * 186 * Params: 187 * srcbuf = buffer containing the data to compress 188 * level = compression level. Legal values are -1 .. 9, with -1 indicating 189 * the default level (6), 0 indicating no compression, 1 being the 190 * least compression and 9 being the most. 191 * 192 * Returns: 193 * the compressed data 194 */ 195 196 ubyte[] compress(const(void)[] srcbuf, int level) 197 in 198 { 199 assert(-1 <= level && level <= 9, "Compression level needs to be within [-1, 9]."); 200 } 201 do 202 { 203 import core.memory : GC; 204 import std.array : uninitializedArray; 205 auto destlen = srcbuf.length + ((srcbuf.length + 1023) / 1024) + 12; 206 auto destbuf = uninitializedArray!(ubyte[])(destlen); 207 auto err = etc.c.zlib.compress2(destbuf.ptr, &destlen, cast(ubyte *) srcbuf.ptr, srcbuf.length, level); 208 if (err) 209 { 210 GC.free(destbuf.ptr); 211 throw new ZlibException(err); 212 } 213 214 destbuf.length = destlen; 215 return destbuf; 216 } 217 218 /********************************************* 219 * ditto 220 */ 221 222 ubyte[] compress(const(void)[] srcbuf) 223 { 224 return compress(srcbuf, Z_DEFAULT_COMPRESSION); 225 } 226 227 /********************************************* 228 * Decompresses the data in srcbuf[]. 229 * Params: 230 * srcbuf = buffer containing the compressed data. 231 * destlen = size of the uncompressed data. 232 * It need not be accurate, but the decompression will be faster 233 * if the exact size is supplied. 234 * winbits = the base two logarithm of the maximum window size. 235 * Returns: the decompressed data. 236 */ 237 238 void[] uncompress(const(void)[] srcbuf, size_t destlen = 0u, int winbits = 15) 239 { 240 import std.conv : to; 241 int err; 242 ubyte[] destbuf; 243 244 if (!destlen) 245 destlen = srcbuf.length * 2 + 1; 246 247 etc.c.zlib.z_stream zs; 248 zs.next_in = cast(typeof(zs.next_in)) srcbuf.ptr; 249 zs.avail_in = to!uint(srcbuf.length); 250 err = etc.c.zlib.inflateInit2(&zs, winbits); 251 if (err) 252 { 253 throw new ZlibException(err); 254 } 255 256 size_t olddestlen = 0u; 257 258 loop: 259 while (true) 260 { 261 destbuf.length = destlen; 262 zs.next_out = cast(typeof(zs.next_out)) &destbuf[olddestlen]; 263 zs.avail_out = to!uint(destlen - olddestlen); 264 olddestlen = destlen; 265 266 err = etc.c.zlib.inflate(&zs, Z_NO_FLUSH); 267 switch (err) 268 { 269 case Z_OK: 270 destlen = destbuf.length * 2; 271 continue loop; 272 273 case Z_STREAM_END: 274 destbuf.length = zs.total_out; 275 err = etc.c.zlib.inflateEnd(&zs); 276 if (err != Z_OK) 277 throw new ZlibException(err); 278 return destbuf; 279 280 default: 281 etc.c.zlib.inflateEnd(&zs); 282 throw new ZlibException(err); 283 } 284 } 285 assert(0, "Unreachable code"); 286 } 287 288 @system unittest 289 { 290 auto src = 291 "the quick brown fox jumps over the lazy dog\r 292 the quick brown fox jumps over the lazy dog\r 293 "; 294 ubyte[] dst; 295 ubyte[] result; 296 297 //arrayPrint(src); 298 dst = compress(src); 299 //arrayPrint(dst); 300 result = cast(ubyte[]) uncompress(dst); 301 //arrayPrint(result); 302 assert(result == src); 303 } 304 305 @system unittest 306 { 307 ubyte[] src = new ubyte[1000000]; 308 ubyte[] dst; 309 ubyte[] result; 310 311 src[] = 0x80; 312 dst = compress(src); 313 assert(dst.length*2 + 1 < src.length); 314 result = cast(ubyte[]) uncompress(dst); 315 assert(result == src); 316 } 317 318 /+ 319 void arrayPrint(ubyte[] array) 320 { 321 //printf("array %p,%d\n", cast(void*) array, array.length); 322 for (size_t i = 0; i < array.length; i++) 323 { 324 printf("%02x ", array[i]); 325 if (((i + 1) & 15) == 0) 326 printf("\n"); 327 } 328 printf("\n\n"); 329 } 330 +/ 331 332 /// the header format the compressed stream is wrapped in 333 enum HeaderFormat { 334 deflate, /// a standard zlib header 335 gzip, /// a gzip file format header 336 determineFromData /// used when decompressing. Try to automatically detect the stream format by looking at the data 337 } 338 339 /********************************************* 340 * Used when the data to be compressed is not all in one buffer. 341 */ 342 343 class Compress 344 { 345 import std.conv : to; 346 347 private: 348 z_stream zs; 349 int level = Z_DEFAULT_COMPRESSION; 350 int inited; 351 immutable bool gzip; 352 353 void error(int err) 354 { 355 if (inited) 356 { deflateEnd(&zs); 357 inited = 0; 358 } 359 throw new ZlibException(err); 360 } 361 362 public: 363 364 /** 365 * Constructor. 366 * 367 * Params: 368 * level = compression level. Legal values are 1 .. 9, with 1 being the least 369 * compression and 9 being the most. The default value is 6. 370 * header = sets the compression type to one of the options available 371 * in $(LREF HeaderFormat). Defaults to HeaderFormat.deflate. 372 * 373 * See_Also: 374 * $(LREF compress), $(LREF HeaderFormat) 375 */ 376 this(int level, HeaderFormat header = HeaderFormat.deflate) 377 in 378 { 379 assert(1 <= level && level <= 9, "Legal compression level are in [1, 9]."); 380 } 381 do 382 { 383 this.level = level; 384 this.gzip = header == HeaderFormat.gzip; 385 } 386 387 /// ditto 388 this(HeaderFormat header = HeaderFormat.deflate) 389 { 390 this.gzip = header == HeaderFormat.gzip; 391 } 392 393 ~this() 394 { int err; 395 396 if (inited) 397 { 398 inited = 0; 399 deflateEnd(&zs); 400 } 401 } 402 403 /** 404 * Compress the data in buf and return the compressed data. 405 * Params: 406 * buf = data to compress 407 * 408 * Returns: 409 * the compressed data. The buffers returned from successive calls to this should be concatenated together. 410 * 411 */ 412 const(void)[] compress(const(void)[] buf) 413 { 414 import core.memory : GC; 415 import std.array : uninitializedArray; 416 int err; 417 ubyte[] destbuf; 418 419 if (buf.length == 0) 420 return null; 421 422 if (!inited) 423 { 424 err = deflateInit2(&zs, level, Z_DEFLATED, 15 + (gzip ? 16 : 0), 8, Z_DEFAULT_STRATEGY); 425 if (err) 426 error(err); 427 inited = 1; 428 } 429 430 destbuf = uninitializedArray!(ubyte[])(zs.avail_in + buf.length); 431 zs.next_out = destbuf.ptr; 432 zs.avail_out = to!uint(destbuf.length); 433 434 if (zs.avail_in) 435 buf = zs.next_in[0 .. zs.avail_in] ~ cast(ubyte[]) buf; 436 437 zs.next_in = cast(typeof(zs.next_in)) buf.ptr; 438 zs.avail_in = to!uint(buf.length); 439 440 err = deflate(&zs, Z_NO_FLUSH); 441 if (err != Z_STREAM_END && err != Z_OK) 442 { 443 GC.free(destbuf.ptr); 444 error(err); 445 } 446 destbuf.length = destbuf.length - zs.avail_out; 447 return destbuf; 448 } 449 450 /*** 451 * Compress and return any remaining data. 452 * The returned data should be appended to that returned by compress(). 453 * Params: 454 * mode = one of the following: 455 * $(DL 456 $(DT Z_SYNC_FLUSH ) 457 $(DD Syncs up flushing to the next byte boundary. 458 Used when more data is to be compressed later on.) 459 $(DT Z_FULL_FLUSH ) 460 $(DD Syncs up flushing to the next byte boundary. 461 Used when more data is to be compressed later on, 462 and the decompressor needs to be restartable at this 463 point.) 464 $(DT Z_FINISH) 465 $(DD (default) Used when finished compressing the data. ) 466 ) 467 */ 468 void[] flush(int mode = Z_FINISH) 469 in 470 { 471 assert(mode == Z_FINISH || mode == Z_SYNC_FLUSH || mode == Z_FULL_FLUSH, 472 "Mode must be either Z_FINISH, Z_SYNC_FLUSH or Z_FULL_FLUSH."); 473 } 474 do 475 { 476 import core.memory : GC; 477 ubyte[] destbuf; 478 ubyte[512] tmpbuf = void; 479 int err; 480 481 if (!inited) 482 return null; 483 484 /* may be zs.avail_out+<some constant> 485 * zs.avail_out is set nonzero by deflate in previous compress() 486 */ 487 //tmpbuf = new void[zs.avail_out]; 488 zs.next_out = tmpbuf.ptr; 489 zs.avail_out = tmpbuf.length; 490 491 while ( (err = deflate(&zs, mode)) != Z_STREAM_END) 492 { 493 if (err == Z_OK) 494 { 495 if (zs.avail_out != 0 && mode != Z_FINISH) 496 break; 497 else if (zs.avail_out == 0) 498 { 499 destbuf ~= tmpbuf; 500 zs.next_out = tmpbuf.ptr; 501 zs.avail_out = tmpbuf.length; 502 continue; 503 } 504 err = Z_BUF_ERROR; 505 } 506 GC.free(destbuf.ptr); 507 error(err); 508 } 509 destbuf ~= tmpbuf[0 .. (tmpbuf.length - zs.avail_out)]; 510 511 if (mode == Z_FINISH) 512 { 513 err = deflateEnd(&zs); 514 inited = 0; 515 if (err) 516 error(err); 517 } 518 return destbuf; 519 } 520 } 521 522 /****** 523 * Used when the data to be decompressed is not all in one buffer. 524 */ 525 526 class UnCompress 527 { 528 import std.conv : to; 529 530 private: 531 z_stream zs; 532 int inited; 533 int done; 534 bool inputEnded; 535 size_t destbufsize; 536 537 HeaderFormat format; 538 539 void error(int err) 540 { 541 if (inited) 542 { inflateEnd(&zs); 543 inited = 0; 544 } 545 throw new ZlibException(err); 546 } 547 548 public: 549 550 /** 551 * Construct. destbufsize is the same as for D.zlib.uncompress(). 552 */ 553 this(uint destbufsize) 554 { 555 this.destbufsize = destbufsize; 556 } 557 558 /** ditto */ 559 this(HeaderFormat format = HeaderFormat.determineFromData) 560 { 561 this.format = format; 562 } 563 564 ~this() 565 { int err; 566 567 if (inited) 568 { 569 inited = 0; 570 inflateEnd(&zs); 571 } 572 done = 1; 573 } 574 575 /** 576 * Decompress the data in buf and return the decompressed data. 577 * The buffers returned from successive calls to this should be concatenated 578 * together. 579 */ 580 const(void)[] uncompress(const(void)[] buf) 581 in 582 { 583 assert(!done, "Buffer has been flushed."); 584 } 585 do 586 { 587 if (inputEnded || !buf.length) 588 return null; 589 590 import core.memory : GC; 591 import std.array : uninitializedArray; 592 int err; 593 594 if (!inited) 595 { 596 int windowBits = 15; 597 if (format == HeaderFormat.gzip) 598 windowBits += 16; 599 else if (format == HeaderFormat.determineFromData) 600 windowBits += 32; 601 602 err = inflateInit2(&zs, windowBits); 603 if (err) 604 error(err); 605 inited = 1; 606 } 607 608 if (!destbufsize) 609 destbufsize = to!uint(buf.length) * 2; 610 auto destbuf = uninitializedArray!(ubyte[])(destbufsize); 611 size_t destFill; 612 613 zs.next_in = cast(ubyte*) buf.ptr; 614 zs.avail_in = to!uint(buf.length); 615 616 while (true) 617 { 618 auto oldAvailIn = zs.avail_in; 619 620 zs.next_out = destbuf[destFill .. $].ptr; 621 zs.avail_out = to!uint(destbuf.length - destFill); 622 623 err = inflate(&zs, Z_NO_FLUSH); 624 if (err == Z_STREAM_END) 625 { 626 inputEnded = true; 627 break; 628 } 629 else if (err != Z_OK) 630 { 631 GC.free(destbuf.ptr); 632 error(err); 633 } 634 else if (!zs.avail_in) 635 break; 636 637 /* 638 According to the zlib manual inflate() stops when either there's 639 no more data to uncompress or the output buffer is full 640 So at this point, the output buffer is too full 641 */ 642 643 destFill = destbuf.length; 644 645 if (destbuf.capacity) 646 { 647 if (destbuf.length < destbuf.capacity) 648 destbuf.length = destbuf.capacity; 649 else 650 { 651 auto newLength = GC.extend(destbuf.ptr, destbufsize, destbufsize); 652 653 if (newLength && destbuf.length < destbuf.capacity) 654 destbuf.length = destbuf.capacity; 655 else 656 destbuf.length += destbufsize; 657 } 658 } 659 else 660 destbuf.length += destbufsize; 661 } 662 663 destbuf.length = destbuf.length - zs.avail_out; 664 return destbuf; 665 } 666 667 // Test for https://issues.dlang.org/show_bug.cgi?id=3191 and 668 // https://issues.dlang.org/show_bug.cgi?id=9505 669 @system unittest 670 { 671 import std.algorithm.comparison; 672 import std.array; 673 import std.file; 674 import std.zlib; 675 676 // Data that can be easily compressed 677 ubyte[1024] originalData; 678 679 // This should yield a compression ratio of at least 1/2 680 auto compressedData = compress(originalData, 9); 681 assert(compressedData.length < originalData.length / 2, 682 "The compression ratio is too low to accurately test this situation"); 683 684 auto chunkSize = compressedData.length / 4; 685 assert(chunkSize < compressedData.length, 686 "The length of the compressed data is too small to accurately test this situation"); 687 688 auto decompressor = new UnCompress(); 689 ubyte[originalData.length] uncompressedData; 690 ubyte[] reusedBuf; 691 int progress; 692 693 reusedBuf.length = chunkSize; 694 695 for (int i = 0; i < compressedData.length; i += chunkSize) 696 { 697 auto len = min(chunkSize, compressedData.length - i); 698 // simulate reading from a stream in small chunks 699 reusedBuf[0 .. len] = compressedData[i .. i + len]; 700 701 // decompress using same input buffer 702 auto chunk = decompressor.uncompress(reusedBuf); 703 assert(progress + chunk.length <= originalData.length, 704 "The uncompressed result is bigger than the original data"); 705 706 uncompressedData[progress .. progress + chunk.length] = cast(const ubyte[]) chunk[]; 707 progress += chunk.length; 708 } 709 710 auto chunk = decompressor.flush(); 711 assert(progress + chunk.length <= originalData.length, 712 "The uncompressed result is bigger than the original data"); 713 714 uncompressedData[progress .. progress + chunk.length] = cast(const ubyte[]) chunk[]; 715 progress += chunk.length; 716 717 assert(progress == originalData.length, 718 "The uncompressed and the original data sizes differ"); 719 assert(originalData[] == uncompressedData[], 720 "The uncompressed and the original data differ"); 721 } 722 723 @system unittest 724 { 725 ubyte[1024] invalidData; 726 auto decompressor = new UnCompress(); 727 728 try 729 { 730 auto uncompressedData = decompressor.uncompress(invalidData); 731 } 732 catch (ZlibException e) 733 { 734 assert(e.msg == "data error"); 735 return; 736 } 737 738 assert(false, "Corrupted data didn't result in an error"); 739 } 740 741 @system unittest 742 { 743 ubyte[2014] originalData = void; 744 auto compressedData = compress(originalData, 9); 745 746 auto decompressor = new UnCompress(); 747 auto uncompressedData = decompressor.uncompress(compressedData ~ cast(ubyte[]) "whatever"); 748 749 assert(originalData.length == uncompressedData.length, 750 "The uncompressed and the original data sizes differ"); 751 assert(originalData[] == uncompressedData[], 752 "The uncompressed and the original data differ"); 753 assert(!decompressor.uncompress("whatever").length, 754 "Compression continued after the end"); 755 } 756 757 /** 758 * Decompress and return any remaining data. 759 * The returned data should be appended to that returned by uncompress(). 760 * The UnCompress object cannot be used further. 761 */ 762 void[] flush() 763 in 764 { 765 assert(!done, "Buffer has been flushed before."); 766 } 767 out 768 { 769 assert(done, "Flushing failed."); 770 } 771 do 772 { 773 done = 1; 774 return null; 775 } 776 777 /// Returns true if all input data has been decompressed and no further data 778 /// can be decompressed (inflate() returned Z_STREAM_END) 779 @property bool empty() const 780 { 781 return inputEnded; 782 } 783 784 /// 785 @system unittest 786 { 787 // some random data 788 ubyte[1024] originalData = void; 789 790 // append garbage data (or don't, this works in both cases) 791 auto compressedData = cast(ubyte[]) compress(originalData) ~ cast(ubyte[]) "whatever"; 792 793 auto decompressor = new UnCompress(); 794 auto uncompressedData = decompressor.uncompress(compressedData); 795 796 assert(uncompressedData[] == originalData[], 797 "The uncompressed and the original data differ"); 798 assert(decompressor.empty, "The UnCompressor reports not being done"); 799 } 800 } 801 802 /* ========================== unittest ========================= */ 803 804 import std.random; 805 import std.stdio; 806 807 @system unittest // by Dave 808 { 809 debug(zlib) writeln("std.zlib.unittest"); 810 811 bool CompressThenUncompress (void[] src) 812 { 813 ubyte[] dst = std.zlib.compress(src); 814 double ratio = (dst.length / cast(double) src.length); 815 debug(zlib) writef("src.length: %1$d, dst: %2$d, Ratio = %3$f", src.length, dst.length, ratio); 816 ubyte[] uncompressedBuf; 817 uncompressedBuf = cast(ubyte[]) std.zlib.uncompress(dst); 818 assert(src.length == uncompressedBuf.length); 819 assert(src == uncompressedBuf); 820 821 return true; 822 } 823 824 825 // smallish buffers 826 for (int idx = 0; idx < 25; idx++) 827 { 828 char[] buf = new char[uniform(0, 100)]; 829 830 // Alternate between more & less compressible 831 foreach (ref char c; buf) 832 c = cast(char) (' ' + (uniform(0, idx % 2 ? 91 : 2))); 833 834 if (CompressThenUncompress(buf)) 835 { 836 debug(zlib) writeln("; Success."); 837 } 838 else 839 { 840 return; 841 } 842 } 843 844 // larger buffers 845 for (int idx = 0; idx < 25; idx++) 846 { 847 char[] buf = new char[uniform(0, 1000/*0000*/)]; 848 849 // Alternate between more & less compressible 850 foreach (ref char c; buf) 851 c = cast(char) (' ' + (uniform(0, idx % 2 ? 91 : 10))); 852 853 if (CompressThenUncompress(buf)) 854 { 855 debug(zlib) writefln("; Success."); 856 } 857 else 858 { 859 return; 860 } 861 } 862 863 debug(zlib) writefln("PASSED std.zlib.unittest"); 864 } 865 866 867 @system unittest // by Artem Rebrov 868 { 869 Compress cmp = new Compress; 870 UnCompress decmp = new UnCompress; 871 872 const(void)[] input; 873 input = "tesatdffadf"; 874 875 const(void)[] buf = cmp.compress(input); 876 buf ~= cmp.flush(); 877 const(void)[] output = decmp.uncompress(buf); 878 879 //writefln("input = '%s'", cast(char[]) input); 880 //writefln("output = '%s'", cast(char[]) output); 881 assert( output[] == input[] ); 882 } 883 884 // https://issues.dlang.org/show_bug.cgi?id=15457 885 @system unittest 886 { 887 static assert(__traits(compiles, etc.c.zlib.gzclose(null))); 888 }