1use std::{
4 fmt::{Debug, Display},
5 fs::File,
6 io::{BufReader, Read, Write},
7 num::TryFromIntError,
8};
9
10use alpm_types::CompressionAlgorithmFileExtension;
11use bzip2::{bufread::BzDecoder, write::BzEncoder};
12use flate2::{bufread::GzDecoder, write::GzEncoder};
13use liblzma::{bufread::XzDecoder, write::XzEncoder};
14use log::trace;
15use zstd::{Decoder, Encoder};
16
17#[derive(Debug, thiserror::Error)]
19pub enum Error {
20 #[error(
22 "Error creating a Zstandard encoder while {context} with {compression_settings:?}:\n{source}"
23 )]
24 CreateZstandardEncoder {
25 context: &'static str,
30 compression_settings: CompressionSettings,
32 source: std::io::Error,
34 },
35
36 #[error("Error creating a Zstandard decoder:\n{0}")]
38 CreateZstandardDecoder(#[source] std::io::Error),
39
40 #[error("Error while finishing {compression_type} compression encoder:\n{source}")]
42 FinishEncoder {
43 compression_type: CompressionAlgorithmFileExtension,
45 source: std::io::Error,
47 },
48
49 #[error("Error while trying to get available parallelism:\n{0}")]
51 GetParallelism(#[source] std::io::Error),
52
53 #[error("Error while trying to convert an integer:\n{0}")]
55 IntegerConversion(#[source] TryFromIntError),
56
57 #[error("Invalid compression level {level} (must be in the range {min} - {max})")]
59 InvalidCompressionLevel {
60 level: u8,
62 min: u8,
64 max: u8,
66 },
67
68 #[error("Unsupported compression algorithm: {value}")]
70 UnsupportedCompressionAlgorithm {
71 value: String,
73 },
74}
75
76#[derive(Clone, Debug, Eq, PartialEq)]
78pub enum CompressionAlgorithm {
79 Bzip2,
81 Gzip,
83 Xz,
85 Zstd,
87}
88
89impl TryFrom<CompressionAlgorithmFileExtension> for CompressionAlgorithm {
90 type Error = Error;
91
92 fn try_from(value: CompressionAlgorithmFileExtension) -> Result<Self, Self::Error> {
94 match value {
95 CompressionAlgorithmFileExtension::Bzip2 => Ok(Self::Bzip2),
96 CompressionAlgorithmFileExtension::Gzip => Ok(Self::Gzip),
97 CompressionAlgorithmFileExtension::Xz => Ok(Self::Xz),
98 CompressionAlgorithmFileExtension::Zstd => Ok(Self::Zstd),
99 _ => Err(Error::UnsupportedCompressionAlgorithm {
100 value: value.to_string(),
101 }),
102 }
103 }
104}
105
106impl From<&CompressionSettings> for CompressionAlgorithm {
107 fn from(value: &CompressionSettings) -> Self {
109 match value {
110 CompressionSettings::Bzip2 { .. } => CompressionAlgorithm::Bzip2,
111 CompressionSettings::Gzip { .. } => CompressionAlgorithm::Gzip,
112 CompressionSettings::Xz { .. } => CompressionAlgorithm::Xz,
113 CompressionSettings::Zstd { .. } => CompressionAlgorithm::Zstd,
114 }
115 }
116}
117
118macro_rules! define_compression_level {
124 (
125 $name:ident,
126 Min => $min:expr,
127 Max => $max:expr,
128 Default => $default:expr,
129 $compression:literal,
130 $url:literal
131 ) => {
132 #[doc = concat!("Compression level for ", $compression, " compression.")]
133 #[derive(Clone, Debug, Eq, PartialEq)]
134 pub struct $name(u8);
135
136 impl $name {
137 #[doc = concat!("Creates a new [`", stringify!($name), "`] from a [`u8`].")]
138 #[doc = concat!("The `level` must be in the range of [`", stringify!($name), "::min`] and [`", stringify!($name), "::max`].")]
140 #[doc = concat!("Returns an error if the value is not in the range of [`", stringify!($name), "::min`] and [`", stringify!($name), "::max`].")]
144 pub fn new(level: u8) -> Result<Self, Error> {
145 trace!(concat!("Creating new compression level for ", $compression, " compression with {{level}}"));
146 if !($name::min()..=$name::max()).contains(&level) {
147 return Err(Error::InvalidCompressionLevel {
148 level,
149 min: $name::min(),
150 max: $name::max(),
151 });
152 }
153 Ok(Self(level))
154 }
155
156 #[doc = concat!("Returns the default level (`", stringify!($default), "`) for [`", stringify!($name), "`].")]
157 #[doc = concat!("The default level adheres to the one selected by the [", $compression, "] executable.")]
159 #[doc = concat!("[", $compression, "]: ", $url)]
161 pub const fn default_level() -> u8 {
162 $default
163 }
164
165 #[doc = concat!("Returns the minimum allowed level (`", stringify!($min), "`) for [`", stringify!($name), "`].")]
166 pub const fn min() -> u8 {
167 $min
168 }
169
170 #[doc = concat!("Returns the maximum allowed level (`", stringify!($max), "`) for [`", stringify!($name), "`].")]
171 pub const fn max() -> u8 {
172 $max
173 }
174 }
175
176 impl Default for $name {
177 #[doc = concat!("Returns the default [`", stringify!($name), "`].")]
178 #[doc = concat!("Delegates to [`", stringify!($name), "::default_level`] for retrieving the default compression level.")]
180 fn default() -> Self {
181 Self($name::default_level())
182 }
183 }
184
185 impl Display for $name {
186 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 write!(f, "{}", self.0)
188 }
189 }
190
191 impl From<&$name> for i32 {
192 fn from(value: &$name) -> Self {
193 i32::from(value.0)
194 }
195 }
196
197 impl From<&$name> for u32 {
198 fn from(value: &$name) -> Self {
199 u32::from(value.0)
200 }
201 }
202
203 impl TryFrom<i8> for $name {
204 type Error = Error;
205
206 fn try_from(value: i8) -> Result<Self, Error> {
207 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
208 }
209 }
210
211 impl TryFrom<i16> for $name {
212 type Error = Error;
213
214 fn try_from(value: i16) -> Result<Self, Error> {
215 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
216 }
217 }
218
219 impl TryFrom<i32> for $name {
220 type Error = Error;
221
222 fn try_from(value: i32) -> Result<Self, Error> {
223 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
224 }
225 }
226
227 impl TryFrom<i64> for $name {
228 type Error = Error;
229
230 fn try_from(value: i64) -> Result<Self, Error> {
231 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
232 }
233 }
234
235 impl TryFrom<u8> for $name {
236 type Error = Error;
237
238 fn try_from(value: u8) -> Result<Self, Error> {
239 $name::new(value)
240 }
241 }
242
243 impl TryFrom<u16> for $name {
244 type Error = Error;
245
246 fn try_from(value: u16) -> Result<Self, Error> {
247 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
248 }
249 }
250
251 impl TryFrom<u32> for $name {
252 type Error = Error;
253
254 fn try_from(value: u32) -> Result<Self, Error> {
255 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
256 }
257 }
258
259 impl TryFrom<u64> for $name {
260 type Error = Error;
261
262 fn try_from(value: u64) -> Result<Self, Error> {
263 $name::new(u8::try_from(value).map_err(Error::IntegerConversion)?)
264 }
265 }
266 };
267}
268
269define_compression_level!(
271 Bzip2CompressionLevel,
272 Min => 1,
273 Max => 9,
274 Default => 9,
275 "bzip2",
276 "https://man.archlinux.org/man/bzip2.1"
277);
278
279define_compression_level!(
281 GzipCompressionLevel,
282 Min => 1,
283 Max => 9,
284 Default => 6,
285 "gzip",
286 "https://man.archlinux.org/man/gzip.1"
287);
288
289define_compression_level!(
291 XzCompressionLevel,
292 Min => 0,
293 Max => 9,
294 Default => 6,
295 "xz",
296 "https://man.archlinux.org/man/xz.1"
297);
298
299define_compression_level!(
301 ZstdCompressionLevel,
302 Min => 0,
303 Max => 22,
304 Default => 3,
305 "zstd",
306 "https://man.archlinux.org/man/zstd.1"
307);
308
309#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
316pub struct ZstdThreads(u32);
317
318impl ZstdThreads {
319 pub fn new(threads: u32) -> Self {
321 Self(threads)
322 }
323
324 pub fn all() -> Self {
328 Self(0)
329 }
330}
331
332impl Default for ZstdThreads {
333 fn default() -> Self {
339 Self(1)
340 }
341}
342
343#[derive(Clone, Debug, Eq, PartialEq)]
345pub enum CompressionSettings {
346 Bzip2 {
348 compression_level: Bzip2CompressionLevel,
350 },
351
352 Gzip {
354 compression_level: GzipCompressionLevel,
356 },
357
358 Xz {
360 compression_level: XzCompressionLevel,
362 },
363
364 Zstd {
366 compression_level: ZstdCompressionLevel,
368 threads: ZstdThreads,
370 },
371}
372
373impl Default for CompressionSettings {
374 fn default() -> Self {
380 Self::Zstd {
381 compression_level: ZstdCompressionLevel::default(),
382 threads: ZstdThreads::default(),
383 }
384 }
385}
386
387impl From<&CompressionSettings> for CompressionAlgorithmFileExtension {
388 fn from(value: &CompressionSettings) -> Self {
390 match value {
391 CompressionSettings::Bzip2 { .. } => CompressionAlgorithmFileExtension::Bzip2,
392 CompressionSettings::Gzip { .. } => CompressionAlgorithmFileExtension::Gzip,
393 CompressionSettings::Xz { .. } => CompressionAlgorithmFileExtension::Xz,
394 CompressionSettings::Zstd { .. } => CompressionAlgorithmFileExtension::Zstd,
395 }
396 }
397}
398
399fn create_zstd_encoder(
414 file: File,
415 compression_level: &ZstdCompressionLevel,
416 threads: &ZstdThreads,
417 settings: &CompressionSettings,
418) -> Result<Encoder<'static, File>, Error> {
419 let mut encoder = Encoder::new(file, compression_level.into()).map_err(|source| {
420 Error::CreateZstandardEncoder {
421 context: "initializing",
422 compression_settings: settings.clone(),
423 source,
424 }
425 })?;
426 encoder
428 .include_checksum(true)
429 .map_err(|source| Error::CreateZstandardEncoder {
430 context: "setting checksums to be added",
431 compression_settings: settings.clone(),
432 source,
433 })?;
434
435 let threads = match threads {
437 ZstdThreads(0) => {
441 u32::try_from(num_cpus::get_physical()).map_err(Error::IntegerConversion)?
442 }
443 ZstdThreads(threads) => *threads,
444 };
445
446 encoder
448 .multithread(threads)
449 .map_err(|source| Error::CreateZstandardEncoder {
450 context: "setting checksums to be added",
451 compression_settings: settings.clone(),
452 source,
453 })?;
454
455 Ok(encoder)
456}
457
458pub enum CompressionEncoder<'a> {
463 Bzip2(BzEncoder<File>),
465
466 Gzip(GzEncoder<File>),
468
469 Xz(XzEncoder<File>),
471
472 Zstd(Encoder<'a, File>),
474}
475
476impl CompressionEncoder<'_> {
477 pub fn new(file: File, settings: &CompressionSettings) -> Result<Self, Error> {
487 Ok(match settings {
488 CompressionSettings::Bzip2 { compression_level } => Self::Bzip2(BzEncoder::new(
489 file,
490 bzip2::Compression::new(compression_level.into()),
491 )),
492 CompressionSettings::Gzip { compression_level } => Self::Gzip(GzEncoder::new(
493 file,
494 flate2::Compression::new(compression_level.into()),
495 )),
496 CompressionSettings::Xz { compression_level } => {
497 Self::Xz(XzEncoder::new_parallel(file, compression_level.into()))
498 }
499 CompressionSettings::Zstd {
500 compression_level,
501 threads,
502 } => Self::Zstd(create_zstd_encoder(
503 file,
504 compression_level,
505 threads,
506 settings,
507 )?),
508 })
509 }
510
511 pub fn finish(self) -> Result<File, Error> {
517 match self {
518 CompressionEncoder::Bzip2(encoder) => {
519 encoder.finish().map_err(|source| Error::FinishEncoder {
520 compression_type: CompressionAlgorithmFileExtension::Bzip2,
521 source,
522 })
523 }
524 CompressionEncoder::Gzip(encoder) => {
525 encoder.finish().map_err(|source| Error::FinishEncoder {
526 compression_type: CompressionAlgorithmFileExtension::Gzip,
527 source,
528 })
529 }
530 CompressionEncoder::Xz(encoder) => {
531 encoder.finish().map_err(|source| Error::FinishEncoder {
532 compression_type: CompressionAlgorithmFileExtension::Xz,
533 source,
534 })
535 }
536 CompressionEncoder::Zstd(encoder) => {
537 encoder.finish().map_err(|source| Error::FinishEncoder {
538 compression_type: CompressionAlgorithmFileExtension::Zstd,
539 source,
540 })
541 }
542 }
543 }
544}
545
546impl Debug for CompressionEncoder<'_> {
547 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
548 write!(
549 f,
550 "CompressionEncoder({})",
551 match self {
552 CompressionEncoder::Bzip2(_) => "Bzip2",
553 CompressionEncoder::Gzip(_) => "Gzip",
554 CompressionEncoder::Xz(_) => "Xz",
555 CompressionEncoder::Zstd(_) => "Zstd",
556 }
557 )
558 }
559}
560
561impl Write for CompressionEncoder<'_> {
562 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
563 match self {
564 CompressionEncoder::Bzip2(encoder) => encoder.write(buf),
565 CompressionEncoder::Gzip(encoder) => encoder.write(buf),
566 CompressionEncoder::Xz(encoder) => encoder.write(buf),
567 CompressionEncoder::Zstd(encoder) => encoder.write(buf),
568 }
569 }
570
571 fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
572 match self {
573 CompressionEncoder::Bzip2(encoder) => encoder.write_vectored(bufs),
574 CompressionEncoder::Gzip(encoder) => encoder.write_vectored(bufs),
575 CompressionEncoder::Xz(encoder) => encoder.write_vectored(bufs),
576 CompressionEncoder::Zstd(encoder) => encoder.write_vectored(bufs),
577 }
578 }
579
580 fn flush(&mut self) -> std::io::Result<()> {
581 match self {
582 CompressionEncoder::Bzip2(encoder) => encoder.flush(),
583 CompressionEncoder::Gzip(encoder) => encoder.flush(),
584 CompressionEncoder::Xz(encoder) => encoder.flush(),
585 CompressionEncoder::Zstd(encoder) => encoder.flush(),
586 }
587 }
588
589 fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
590 match self {
591 CompressionEncoder::Bzip2(encoder) => encoder.write_all(buf),
592 CompressionEncoder::Gzip(encoder) => encoder.write_all(buf),
593 CompressionEncoder::Xz(encoder) => encoder.write_all(buf),
594 CompressionEncoder::Zstd(encoder) => encoder.write_all(buf),
595 }
596 }
597
598 fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> std::io::Result<()> {
599 match self {
600 CompressionEncoder::Bzip2(encoder) => encoder.write_fmt(fmt),
601 CompressionEncoder::Gzip(encoder) => encoder.write_fmt(fmt),
602 CompressionEncoder::Xz(encoder) => encoder.write_fmt(fmt),
603 CompressionEncoder::Zstd(encoder) => encoder.write_fmt(fmt),
604 }
605 }
606
607 fn by_ref(&mut self) -> &mut Self
608 where
609 Self: Sized,
610 {
611 self
612 }
613}
614
615pub enum CompressionDecoder<'a> {
620 Bzip2(BzDecoder<BufReader<File>>),
622
623 Gzip(GzDecoder<BufReader<File>>),
625
626 Xz(XzDecoder<BufReader<File>>),
628
629 Zstd(Decoder<'a, BufReader<File>>),
631}
632
633impl CompressionDecoder<'_> {
634 pub fn new(file: File, algorithm: CompressionAlgorithm) -> Result<Self, Error> {
644 match algorithm {
645 CompressionAlgorithm::Bzip2 => Ok(Self::Bzip2(BzDecoder::new(BufReader::new(file)))),
646 CompressionAlgorithm::Gzip => Ok(Self::Gzip(GzDecoder::new(BufReader::new(file)))),
647 CompressionAlgorithm::Xz => Ok(Self::Xz(XzDecoder::new(BufReader::new(file)))),
648 CompressionAlgorithm::Zstd => Ok(Self::Zstd(
649 Decoder::new(file).map_err(Error::CreateZstandardDecoder)?,
650 )),
651 }
652 }
653}
654
655impl Debug for CompressionDecoder<'_> {
656 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
657 write!(
658 f,
659 "CompressionDecoder({})",
660 match self {
661 CompressionDecoder::Bzip2(_) => "Bzip2",
662 CompressionDecoder::Gzip(_) => "Gzip",
663 CompressionDecoder::Xz(_) => "Xz",
664 CompressionDecoder::Zstd(_) => "Zstd",
665 }
666 )
667 }
668}
669
670impl Read for CompressionDecoder<'_> {
671 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
672 match self {
673 CompressionDecoder::Bzip2(decoder) => decoder.read(buf),
674 CompressionDecoder::Gzip(decoder) => decoder.read(buf),
675 CompressionDecoder::Xz(decoder) => decoder.read(buf),
676 CompressionDecoder::Zstd(decoder) => decoder.read(buf),
677 }
678 }
679
680 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> std::io::Result<usize> {
681 match self {
682 CompressionDecoder::Bzip2(decoder) => decoder.read_to_end(buf),
683 CompressionDecoder::Gzip(decoder) => decoder.read_to_end(buf),
684 CompressionDecoder::Xz(decoder) => decoder.read_to_end(buf),
685 CompressionDecoder::Zstd(decoder) => decoder.read_to_end(buf),
686 }
687 }
688}
689
690#[cfg(test)]
691mod tests {
692 use std::io::{IoSlice, Seek};
693
694 use proptest::{proptest, test_runner::Config as ProptestConfig};
695 use rstest::rstest;
696 use tempfile::tempfile;
697 use testresult::TestResult;
698
699 use super::*;
700
701 proptest! {
702 #![proptest_config(ProptestConfig::with_cases(1000))]
703
704 #[test]
705 fn valid_bzip2_compression_level_try_from_i8(input in 1..=9i8) {
706 assert!(Bzip2CompressionLevel::try_from(input).is_ok());
707 }
708
709 #[test]
710 fn valid_bzip2_compression_level_try_from_i16(input in 1..=9i16) {
711 assert!(Bzip2CompressionLevel::try_from(input).is_ok());
712 }
713
714 #[test]
715 fn valid_bzip2_compression_level_try_from_i32(input in 1..=9i32) {
716 assert!(Bzip2CompressionLevel::try_from(input).is_ok());
717 }
718
719 #[test]
720 fn valid_bzip2_compression_level_try_from_i64(input in 1..=9i64) {
721 assert!(Bzip2CompressionLevel::try_from(input).is_ok());
722 }
723
724 #[test]
725 fn valid_bzip2_compression_level_try_from_u8(input in 1..=9u8) {
726 assert!(Bzip2CompressionLevel::try_from(input).is_ok());
727 }
728
729 #[test]
730 fn valid_bzip2_compression_level_try_from_u16(input in 1..=9u16) {
731 assert!(Bzip2CompressionLevel::try_from(input).is_ok());
732 }
733
734 #[test]
735 fn valid_bzip2_compression_level_try_from_u32(input in 1..=9u32) {
736 assert!(Bzip2CompressionLevel::try_from(input).is_ok());
737 }
738
739 #[test]
740 fn valid_bzip2_compression_level_try_from_u64(input in 1..=9u64) {
741 assert!(Bzip2CompressionLevel::try_from(input).is_ok());
742 }
743
744 #[test]
745 fn valid_gzip_compression_level_try_from_i8(input in 1..=9i8) {
746 assert!(GzipCompressionLevel::try_from(input).is_ok());
747 }
748
749 #[test]
750 fn valid_gzip_compression_level_try_from_i16(input in 1..=9i16) {
751 assert!(GzipCompressionLevel::try_from(input).is_ok());
752 }
753
754 #[test]
755 fn valid_gzip_compression_level_try_from_i32(input in 1..=9i32) {
756 assert!(GzipCompressionLevel::try_from(input).is_ok());
757 }
758
759 #[test]
760 fn valid_gzip_compression_level_try_from_i64(input in 1..=9i64) {
761 assert!(GzipCompressionLevel::try_from(input).is_ok());
762 }
763
764 #[test]
765 fn valid_gzip_compression_level_try_from_u8(input in 1..=9u8) {
766 assert!(GzipCompressionLevel::try_from(input).is_ok());
767 }
768
769 #[test]
770 fn valid_gzip_compression_level_try_from_u16(input in 1..=9u16) {
771 assert!(GzipCompressionLevel::try_from(input).is_ok());
772 }
773
774 #[test]
775 fn valid_gzip_compression_level_try_from_u32(input in 1..=9u32) {
776 assert!(GzipCompressionLevel::try_from(input).is_ok());
777 }
778
779 #[test]
780 fn valid_gzip_compression_level_try_from_u64(input in 1..=9u64) {
781 assert!(GzipCompressionLevel::try_from(input).is_ok());
782 }
783
784 #[test]
785 fn valid_xz_compression_level_try_from_i8(input in 0..=9i8) {
786 assert!(XzCompressionLevel::try_from(input).is_ok());
787 }
788
789 #[test]
790 fn valid_xz_compression_level_try_from_i16(input in 0..=9i16) {
791 assert!(XzCompressionLevel::try_from(input).is_ok());
792 }
793
794 #[test]
795 fn valid_xz_compression_level_try_from_i32(input in 0..=9i32) {
796 assert!(XzCompressionLevel::try_from(input).is_ok());
797 }
798
799 #[test]
800 fn valid_xz_compression_level_try_from_i64(input in 0..=9i64) {
801 assert!(XzCompressionLevel::try_from(input).is_ok());
802 }
803
804 #[test]
805 fn valid_xz_compression_level_try_from_u8(input in 0..=9u8) {
806 assert!(XzCompressionLevel::try_from(input).is_ok());
807 }
808
809 #[test]
810 fn valid_xz_compression_level_try_from_u16(input in 0..=9u16) {
811 assert!(XzCompressionLevel::try_from(input).is_ok());
812 }
813
814 #[test]
815 fn valid_xz_compression_level_try_from_u32(input in 0..=9u32) {
816 assert!(XzCompressionLevel::try_from(input).is_ok());
817 }
818
819 #[test]
820 fn valid_xz_compression_level_try_from_u64(input in 0..=9u64) {
821 assert!(XzCompressionLevel::try_from(input).is_ok());
822 }
823
824 #[test]
825 fn valid_zstd_compression_level_try_from_i8(input in 0..=22i8) {
826 assert!(ZstdCompressionLevel::try_from(input).is_ok());
827 }
828
829 #[test]
830 fn valid_zstd_compression_level_try_from_i16(input in 0..=22i16) {
831 assert!(ZstdCompressionLevel::try_from(input).is_ok());
832 }
833
834 #[test]
835 fn valid_zstd_compression_level_try_from_i32(input in 0..=22i32) {
836 assert!(ZstdCompressionLevel::try_from(input).is_ok());
837 }
838
839 #[test]
840 fn valid_zstd_compression_level_try_from_i64(input in 0..=22i64) {
841 assert!(ZstdCompressionLevel::try_from(input).is_ok());
842 }
843
844 #[test]
845 fn valid_zstd_compression_level_try_from_u8(input in 0..=22u8) {
846 assert!(ZstdCompressionLevel::try_from(input).is_ok());
847 }
848
849 #[test]
850 fn valid_zstd_compression_level_try_from_u16(input in 0..=22u16) {
851 assert!(ZstdCompressionLevel::try_from(input).is_ok());
852 }
853
854 #[test]
855 fn valid_zstd_compression_level_try_from_u32(input in 0..=22u32) {
856 assert!(ZstdCompressionLevel::try_from(input).is_ok());
857 }
858
859 #[test]
860 fn valid_zstd_compression_level_try_from_u64(input in 0..=22u64) {
861 assert!(ZstdCompressionLevel::try_from(input).is_ok());
862 }
863 }
864
865 #[rstest]
866 #[case::too_large(Bzip2CompressionLevel::max() + 1)]
867 #[case::too_small(Bzip2CompressionLevel::min() - 1)]
868 fn create_bzip2_compression_level_fails(#[case] level: u8) -> TestResult {
869 if let Ok(level) = Bzip2CompressionLevel::new(level) {
870 return Err(format!("Should not have succeeded but created level: {level}").into());
871 }
872
873 Ok(())
874 }
875
876 #[test]
877 fn create_bzip2_compression_level_succeeds() -> TestResult {
878 if let Err(error) = Bzip2CompressionLevel::new(6) {
879 return Err(format!("Should have succeeded but raised error:\n{error}").into());
880 }
881
882 Ok(())
883 }
884
885 #[rstest]
886 #[case::too_large(GzipCompressionLevel::max() + 1)]
887 #[case::too_small(GzipCompressionLevel::min() - 1)]
888 fn create_gzip_compression_level_fails(#[case] level: u8) -> TestResult {
889 if let Ok(level) = GzipCompressionLevel::new(level) {
890 return Err(format!("Should not have succeeded but created level: {level}").into());
891 }
892
893 Ok(())
894 }
895
896 #[test]
897 fn create_gzip_compression_level_succeeds() -> TestResult {
898 if let Err(error) = GzipCompressionLevel::new(6) {
899 return Err(format!("Should have succeeded but raised error:\n{error}").into());
900 }
901
902 Ok(())
903 }
904
905 #[test]
906 fn create_xz_compression_level_fails() -> TestResult {
907 if let Ok(level) = XzCompressionLevel::new(XzCompressionLevel::max() + 1) {
908 return Err(format!("Should not have succeeded but created level: {level}").into());
909 }
910
911 Ok(())
912 }
913
914 #[test]
915 fn create_xz_compression_level_succeeds() -> TestResult {
916 if let Err(error) = XzCompressionLevel::new(6) {
917 return Err(format!("Should have succeeded but raised error:\n{error}").into());
918 }
919
920 Ok(())
921 }
922
923 #[test]
924 fn create_zstd_compression_level_fails() -> TestResult {
925 if let Ok(level) = ZstdCompressionLevel::new(ZstdCompressionLevel::max() + 1) {
926 return Err(format!("Should not have succeeded but created level: {level}").into());
927 }
928
929 Ok(())
930 }
931
932 #[test]
933 fn create_zstd_compression_level_succeeds() -> TestResult {
934 if let Err(error) = ZstdCompressionLevel::new(6) {
935 return Err(format!("Should have succeeded but raised error:\n{error}").into());
936 }
937
938 Ok(())
939 }
940
941 #[test]
943 fn default_compression_settings() -> TestResult {
944 assert!(matches!(
945 CompressionSettings::default(),
946 CompressionSettings::Zstd {
947 compression_level: _,
948 threads: _,
949 }
950 ));
951 Ok(())
952 }
953
954 #[rstest]
956 #[case::bzip2(CompressionSettings::Bzip2 { compression_level: Bzip2CompressionLevel::default()})]
957 #[case::gzip(CompressionSettings::Gzip { compression_level: GzipCompressionLevel::default()})]
958 #[case::xz(CompressionSettings::Xz { compression_level: XzCompressionLevel::default()})]
959 #[case::zstd_all_threads(CompressionSettings::Zstd { compression_level: ZstdCompressionLevel::default(), threads: ZstdThreads::new(0) })]
960 #[case::zstd_one_thread(CompressionSettings::Zstd { compression_level: ZstdCompressionLevel::default(), threads: ZstdThreads::new(1) })]
961 #[case::zstd_crazy_threads(CompressionSettings::Zstd { compression_level: ZstdCompressionLevel::default(), threads: ZstdThreads::new(99999) })]
962 fn test_compression_encoder_write(#[case] settings: CompressionSettings) -> TestResult {
963 let file = tempfile()?;
964 let mut encoder = CompressionEncoder::new(file, &settings)?;
965 let ref_encoder = encoder.by_ref();
966 let buf = &[1; 8];
967
968 let mut write_len = 0;
969 while write_len < buf.len() {
970 let len_written = ref_encoder.write(buf)?;
971 write_len += len_written;
972 }
973
974 ref_encoder.flush()?;
975
976 Ok(())
977 }
978
979 #[rstest]
982 #[case::bzip2(CompressionSettings::Bzip2 { compression_level: Bzip2CompressionLevel::default()})]
983 #[case::gzip(CompressionSettings::Gzip { compression_level: GzipCompressionLevel::default()})]
984 #[case::xz(CompressionSettings::Xz { compression_level: XzCompressionLevel::default()})]
985 #[case::zstd_all_threads(CompressionSettings::Zstd { compression_level: ZstdCompressionLevel::default(), threads: ZstdThreads::new(0) })]
986 #[case::zstd_one_thread(CompressionSettings::Zstd { compression_level: ZstdCompressionLevel::default(), threads: ZstdThreads::new(1) })]
987 #[case::zstd_crazy_threads(CompressionSettings::Zstd { compression_level: ZstdCompressionLevel::default(), threads: ZstdThreads::new(99999) })]
988 fn test_compression_encoder_write_vectored(
989 #[case] settings: CompressionSettings,
990 ) -> TestResult {
991 let file = tempfile()?;
992 let mut encoder = CompressionEncoder::new(file, &settings)?;
993 let ref_encoder = encoder.by_ref();
994
995 let data1 = [1; 8];
996 let data2 = [15; 8];
997 let io_slice1 = IoSlice::new(&data1);
998 let io_slice2 = IoSlice::new(&data2);
999
1000 let mut write_len = 0;
1001 while write_len < data1.len() + data2.len() {
1002 let len_written = ref_encoder.write_vectored(&[io_slice1, io_slice2])?;
1003 write_len += len_written;
1004 }
1005
1006 ref_encoder.flush()?;
1007
1008 Ok(())
1009 }
1010
1011 #[rstest]
1013 #[case::bzip2(CompressionSettings::Bzip2 { compression_level: Bzip2CompressionLevel::default()})]
1014 #[case::gzip(CompressionSettings::Gzip { compression_level: GzipCompressionLevel::default()})]
1015 #[case::xz(CompressionSettings::Xz { compression_level: XzCompressionLevel::default()})]
1016 #[case::zstd_all_threads(CompressionSettings::Zstd { compression_level: ZstdCompressionLevel::default(), threads: ZstdThreads::new(0) })]
1017 #[case::zstd_one_thread(CompressionSettings::Zstd { compression_level: ZstdCompressionLevel::default(), threads: ZstdThreads::new(1) })]
1018 #[case::zstd_crazy_threads(CompressionSettings::Zstd { compression_level: ZstdCompressionLevel::default(), threads: ZstdThreads::new(99999) })]
1019 fn test_compression_encoder_write_all(#[case] settings: CompressionSettings) -> TestResult {
1020 let file = tempfile()?;
1021 let mut encoder = CompressionEncoder::new(file, &settings)?;
1022 let ref_encoder = encoder.by_ref();
1023 let buf = &[1; 8];
1024
1025 ref_encoder.write_all(buf)?;
1026
1027 ref_encoder.flush()?;
1028
1029 Ok(())
1030 }
1031
1032 #[rstest]
1034 #[case::bzip2(CompressionSettings::Bzip2 { compression_level: Bzip2CompressionLevel::default()})]
1035 #[case::gzip(CompressionSettings::Gzip { compression_level: GzipCompressionLevel::default()})]
1036 #[case::xz(CompressionSettings::Xz { compression_level: XzCompressionLevel::default()})]
1037 #[case::zstd_all_threads(CompressionSettings::Zstd { compression_level: ZstdCompressionLevel::default(), threads: ZstdThreads::new(0) })]
1038 #[case::zstd_one_thread(CompressionSettings::Zstd { compression_level: ZstdCompressionLevel::default(), threads: ZstdThreads::new(1) })]
1039 #[case::zstd_crazy_threads(CompressionSettings::Zstd { compression_level: ZstdCompressionLevel::default(), threads: ZstdThreads::new(99999) })]
1040 fn test_compression_encoder_write_fmt(#[case] settings: CompressionSettings) -> TestResult {
1041 let file = tempfile()?;
1042 let mut encoder = CompressionEncoder::new(file, &settings)?;
1043 let ref_encoder = encoder.by_ref();
1044
1045 ref_encoder.write_fmt(format_args!("{:.*}", 2, 1.234567))?;
1046
1047 ref_encoder.flush()?;
1048
1049 Ok(())
1050 }
1051
1052 #[rstest]
1055 #[case::bzip2(CompressionAlgorithm::Bzip2, CompressionSettings::Bzip2 {
1056 compression_level: Bzip2CompressionLevel::default()
1057 })]
1058 #[case::gzip(CompressionAlgorithm::Gzip, CompressionSettings::Gzip {
1059 compression_level: GzipCompressionLevel::default()
1060 })]
1061 #[case::xz(CompressionAlgorithm::Xz, CompressionSettings::Xz {
1062 compression_level: XzCompressionLevel::default()
1063 })]
1064 #[case::zstd(CompressionAlgorithm::Zstd, CompressionSettings::Zstd {
1065 compression_level: ZstdCompressionLevel::default(),
1066 threads: ZstdThreads::new(0),
1067 })]
1068 fn test_compression_decoder_roundtrip(
1069 #[case] algorithm: CompressionAlgorithm,
1070 #[case] settings: CompressionSettings,
1071 ) -> TestResult {
1072 let input_data = b"alpm4ever";
1074
1075 let mut file = tempfile()?;
1077 {
1078 let mut encoder = CompressionEncoder::new(file.try_clone()?, &settings)?;
1079 encoder.write_all(input_data)?;
1080 encoder.flush()?;
1081 encoder.finish()?;
1082 }
1083
1084 file.rewind()?;
1086
1087 let mut decoder = CompressionDecoder::new(file, algorithm)?;
1089 let mut output = Vec::new();
1090 decoder.read_to_end(&mut output)?;
1091
1092 assert_eq!(output, input_data);
1094 Ok(())
1095 }
1096
1097 #[rstest]
1100 #[case(CompressionAlgorithmFileExtension::Bzip2, CompressionAlgorithm::Bzip2)]
1101 #[case(CompressionAlgorithmFileExtension::Gzip, CompressionAlgorithm::Gzip)]
1102 #[case(CompressionAlgorithmFileExtension::Xz, CompressionAlgorithm::Xz)]
1103 #[case(CompressionAlgorithmFileExtension::Zstd, CompressionAlgorithm::Zstd)]
1104 fn test_compression_algorithm_conversion_success(
1105 #[case] ext: CompressionAlgorithmFileExtension,
1106 #[case] expected: CompressionAlgorithm,
1107 ) -> TestResult {
1108 let result = CompressionAlgorithm::try_from(ext)?;
1109 assert_eq!(result, expected);
1110 Ok(())
1111 }
1112
1113 #[rstest]
1116 #[case(CompressionAlgorithmFileExtension::Compress, "Z")]
1117 #[case(CompressionAlgorithmFileExtension::Lrzip, "lrz")]
1118 #[case(CompressionAlgorithmFileExtension::Lzip, "lz")]
1119 #[case(CompressionAlgorithmFileExtension::Lz4, "lz4")]
1120 #[case(CompressionAlgorithmFileExtension::Lzop, "lzo")]
1121 fn test_compression_algorithm_conversion_failure(
1122 #[case] ext: CompressionAlgorithmFileExtension,
1123 #[case] expected_str: &str,
1124 ) -> TestResult {
1125 match CompressionAlgorithm::try_from(ext) {
1126 Ok(algorithm) => Err(format!("Expected failure, but got: {algorithm:?}").into()),
1127 Err(Error::UnsupportedCompressionAlgorithm { value }) => {
1128 assert_eq!(value, expected_str);
1129 Ok(())
1130 }
1131 Err(e) => Err(format!("Unexpected error variant: {e:?}").into()),
1132 }
1133 }
1134
1135 #[rstest]
1138 #[case::bzip2(CompressionSettings::Bzip2 {
1139 compression_level: Bzip2CompressionLevel::default()
1140 }, CompressionAlgorithm::Bzip2)]
1141 #[case::gzip(CompressionSettings::Gzip {
1142 compression_level: GzipCompressionLevel::default()
1143 }, CompressionAlgorithm::Gzip)]
1144 #[case::xz(CompressionSettings::Xz {
1145 compression_level: XzCompressionLevel::default()
1146 }, CompressionAlgorithm::Xz)]
1147 #[case::zstd(CompressionSettings::Zstd {
1148 compression_level: ZstdCompressionLevel::default(),
1149 threads: ZstdThreads::new(4),
1150 }, CompressionAlgorithm::Zstd)]
1151 fn test_from_compression_settings_to_algorithm(
1152 #[case] settings: CompressionSettings,
1153 #[case] expected: CompressionAlgorithm,
1154 ) -> TestResult {
1155 let result = CompressionAlgorithm::from(&settings);
1156 assert_eq!(result, expected);
1157 Ok(())
1158 }
1159
1160 #[rstest]
1161 #[case::bzip2(CompressionAlgorithm::Bzip2)]
1162 #[case::gzip(CompressionAlgorithm::Gzip)]
1163 #[case::xz(CompressionAlgorithm::Xz)]
1164 #[case::zstd(CompressionAlgorithm::Zstd)]
1165 fn test_compression_decoder_debug(#[case] algorithm: CompressionAlgorithm) -> TestResult {
1166 let file = tempfile()?;
1167 let decoder = CompressionDecoder::new(file, algorithm)?;
1168 let debug_str = format!("{decoder:?}");
1169 assert!(debug_str.contains("CompressionDecoder"));
1170 Ok(())
1171 }
1172}