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 }