1use std::collections::HashMap;
8use std::io::{Read, Write};
9
10use serde::{Deserialize, Serialize};
11use torsh_core::error::{Result, TorshError};
12
13use crate::resources::{Resource, ResourceType};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub enum CompressionAlgorithm {
18 None,
20 Gzip,
22 Zstd,
24 Lzma,
26 Brotli,
28 Lz4,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34pub struct CompressionLevel(pub u32);
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum CompressionStrategy {
39 Speed,
41 Size,
43 Balanced,
45 Adaptive,
47}
48
49#[derive(Debug, Clone)]
51pub struct CompressionConfig {
52 pub default_algorithm: CompressionAlgorithm,
54 pub default_level: CompressionLevel,
56 pub strategy: CompressionStrategy,
58 pub algorithm_overrides: HashMap<ResourceType, CompressionAlgorithm>,
60 pub min_size_threshold: usize,
62 pub max_memory_size: usize,
64 pub parallel_compression: bool,
66}
67
68pub struct AdvancedCompressor {
70 config: CompressionConfig,
71}
72
73#[derive(Debug, Clone)]
75pub struct CompressionResult {
76 pub data: Vec<u8>,
78 pub algorithm: CompressionAlgorithm,
80 pub level: CompressionLevel,
82 pub original_size: usize,
84 pub compressed_size: usize,
86 pub ratio: f32,
88 pub compression_time_ms: u64,
90}
91
92#[derive(Debug, Clone)]
94pub struct DecompressionResult {
95 pub data: Vec<u8>,
97 pub algorithm: CompressionAlgorithm,
99 pub decompression_time_ms: u64,
101}
102
103impl Default for CompressionLevel {
104 fn default() -> Self {
105 CompressionLevel(6)
106 }
107}
108
109impl CompressionLevel {
110 pub fn new(level: u32) -> Self {
112 CompressionLevel(level)
113 }
114
115 pub fn for_algorithm(&self, algorithm: CompressionAlgorithm) -> u32 {
117 match algorithm {
118 CompressionAlgorithm::None => 0,
119 CompressionAlgorithm::Gzip => self.0.min(9),
120 CompressionAlgorithm::Zstd => self.0.min(22),
121 CompressionAlgorithm::Lzma => self.0.min(9),
122 CompressionAlgorithm::Brotli => self.0.min(11),
123 CompressionAlgorithm::Lz4 => self.0.min(16),
124 }
125 }
126}
127
128impl Default for CompressionConfig {
129 fn default() -> Self {
130 let mut algorithm_overrides = HashMap::new();
131
132 algorithm_overrides.insert(ResourceType::Source, CompressionAlgorithm::Brotli);
134 algorithm_overrides.insert(ResourceType::Config, CompressionAlgorithm::Brotli);
135 algorithm_overrides.insert(ResourceType::Documentation, CompressionAlgorithm::Brotli);
136 algorithm_overrides.insert(ResourceType::Text, CompressionAlgorithm::Brotli);
137 algorithm_overrides.insert(ResourceType::Metadata, CompressionAlgorithm::Brotli);
138
139 algorithm_overrides.insert(ResourceType::Model, CompressionAlgorithm::Zstd);
141 algorithm_overrides.insert(ResourceType::Data, CompressionAlgorithm::Zstd);
142 algorithm_overrides.insert(ResourceType::Binary, CompressionAlgorithm::Zstd);
143
144 Self {
145 default_algorithm: CompressionAlgorithm::Zstd,
146 default_level: CompressionLevel::default(),
147 strategy: CompressionStrategy::Balanced,
148 algorithm_overrides,
149 min_size_threshold: 256, max_memory_size: 100 * 1024 * 1024, parallel_compression: true,
152 }
153 }
154}
155
156impl CompressionConfig {
157 pub fn new() -> Self {
159 Self::default()
160 }
161
162 pub fn with_algorithm(mut self, algorithm: CompressionAlgorithm) -> Self {
164 self.default_algorithm = algorithm;
165 self
166 }
167
168 pub fn with_level(mut self, level: CompressionLevel) -> Self {
170 self.default_level = level;
171 self
172 }
173
174 pub fn with_strategy(mut self, strategy: CompressionStrategy) -> Self {
176 self.strategy = strategy;
177 self
178 }
179
180 pub fn with_min_threshold(mut self, threshold: usize) -> Self {
182 self.min_size_threshold = threshold;
183 self
184 }
185
186 pub fn with_parallel(mut self, parallel: bool) -> Self {
188 self.parallel_compression = parallel;
189 self
190 }
191
192 pub fn algorithm_for_resource(&self, resource_type: ResourceType) -> CompressionAlgorithm {
194 self.algorithm_overrides
195 .get(&resource_type)
196 .copied()
197 .unwrap_or(self.default_algorithm)
198 }
199
200 pub fn level_for_strategy(&self, strategy: CompressionStrategy) -> CompressionLevel {
202 match strategy {
203 CompressionStrategy::Speed => CompressionLevel(1),
204 CompressionStrategy::Size => CompressionLevel(9),
205 CompressionStrategy::Balanced => CompressionLevel(6),
206 CompressionStrategy::Adaptive => self.default_level,
207 }
208 }
209}
210
211impl AdvancedCompressor {
212 pub fn new() -> Self {
214 Self {
215 config: CompressionConfig::default(),
216 }
217 }
218
219 pub fn with_config(config: CompressionConfig) -> Self {
221 Self { config }
222 }
223
224 pub fn compress_resource(&self, resource: &Resource) -> Result<CompressionResult> {
226 if resource.data.len() < self.config.min_size_threshold {
228 return Ok(CompressionResult {
229 data: resource.data.clone(),
230 algorithm: CompressionAlgorithm::None,
231 level: CompressionLevel(0),
232 original_size: resource.data.len(),
233 compressed_size: resource.data.len(),
234 ratio: 1.0,
235 compression_time_ms: 0,
236 });
237 }
238
239 let algorithm = self.config.algorithm_for_resource(resource.resource_type);
241 let level = match self.config.strategy {
242 CompressionStrategy::Adaptive => self.adaptive_level(resource),
243 strategy => self.config.level_for_strategy(strategy),
244 };
245
246 self.compress_data(&resource.data, algorithm, level)
247 }
248
249 pub fn compress_data(
251 &self,
252 data: &[u8],
253 algorithm: CompressionAlgorithm,
254 level: CompressionLevel,
255 ) -> Result<CompressionResult> {
256 let start_time = std::time::Instant::now();
257
258 let compressed_data = match algorithm {
259 CompressionAlgorithm::None => data.to_vec(),
260 CompressionAlgorithm::Gzip => {
261 self.compress_gzip(data, level.for_algorithm(algorithm))?
262 }
263 CompressionAlgorithm::Zstd => {
264 self.compress_zstd(data, level.for_algorithm(algorithm))?
265 }
266 CompressionAlgorithm::Lzma => {
267 self.compress_lzma(data, level.for_algorithm(algorithm))?
268 }
269 CompressionAlgorithm::Brotli => {
270 self.compress_brotli(data, level.for_algorithm(algorithm))?
271 }
272 CompressionAlgorithm::Lz4 => self.compress_lz4(data, level.for_algorithm(algorithm))?,
273 };
274
275 let compression_time_ms = start_time.elapsed().as_millis() as u64;
276 let ratio = if data.is_empty() {
277 1.0
278 } else {
279 compressed_data.len() as f32 / data.len() as f32
280 };
281
282 let compressed_size = compressed_data.len();
283
284 Ok(CompressionResult {
285 data: compressed_data,
286 algorithm,
287 level,
288 original_size: data.len(),
289 compressed_size,
290 ratio,
291 compression_time_ms,
292 })
293 }
294
295 pub fn decompress_data(
297 &self,
298 compressed_data: &[u8],
299 algorithm: CompressionAlgorithm,
300 ) -> Result<DecompressionResult> {
301 let start_time = std::time::Instant::now();
302
303 let decompressed_data = match algorithm {
304 CompressionAlgorithm::None => compressed_data.to_vec(),
305 CompressionAlgorithm::Gzip => self.decompress_gzip(compressed_data)?,
306 CompressionAlgorithm::Zstd => self.decompress_zstd(compressed_data)?,
307 CompressionAlgorithm::Lzma => self.decompress_lzma(compressed_data)?,
308 CompressionAlgorithm::Brotli => self.decompress_brotli(compressed_data)?,
309 CompressionAlgorithm::Lz4 => self.decompress_lz4(compressed_data)?,
310 };
311
312 let decompression_time_ms = start_time.elapsed().as_millis() as u64;
313
314 Ok(DecompressionResult {
315 data: decompressed_data,
316 algorithm,
317 decompression_time_ms,
318 })
319 }
320
321 pub fn benchmark_algorithms(&self, data: &[u8]) -> Result<Vec<CompressionResult>> {
323 let algorithms = [
324 CompressionAlgorithm::Gzip,
325 CompressionAlgorithm::Zstd,
326 CompressionAlgorithm::Lzma,
327 CompressionAlgorithm::Brotli,
328 CompressionAlgorithm::Lz4,
329 ];
330
331 let mut results = Vec::new();
332
333 for algorithm in &algorithms {
334 let result = self.compress_data(data, *algorithm, CompressionLevel(6))?;
335 results.push(result);
336 }
337
338 results.sort_by(|a, b| {
340 a.ratio
341 .partial_cmp(&b.ratio)
342 .unwrap_or(std::cmp::Ordering::Equal)
343 });
344
345 Ok(results)
346 }
347
348 fn adaptive_level(&self, resource: &Resource) -> CompressionLevel {
350 let data_size = resource.data.len();
351
352 match resource.resource_type {
353 ResourceType::Model | ResourceType::Binary => {
354 if data_size > 10 * 1024 * 1024 {
356 CompressionLevel(3) } else if data_size > 1024 * 1024 {
359 CompressionLevel(6) } else {
362 CompressionLevel(9) }
364 }
365 ResourceType::Source | ResourceType::Config | ResourceType::Documentation => {
366 CompressionLevel(8)
368 }
369 ResourceType::Text | ResourceType::Metadata => {
370 CompressionLevel(7)
372 }
373 _ => CompressionLevel(6), }
375 }
376
377 fn compress_gzip(&self, data: &[u8], level: u32) -> Result<Vec<u8>> {
379 use flate2::{write::GzEncoder, Compression};
380
381 let mut encoder = GzEncoder::new(Vec::new(), Compression::new(level));
382 encoder.write_all(data).map_err(|e| {
383 TorshError::SerializationError(format!("Gzip compression failed: {}", e))
384 })?;
385
386 encoder
387 .finish()
388 .map_err(|e| TorshError::SerializationError(format!("Gzip finalization failed: {}", e)))
389 }
390
391 fn decompress_gzip(&self, data: &[u8]) -> Result<Vec<u8>> {
393 use flate2::read::GzDecoder;
394
395 let mut decoder = GzDecoder::new(data);
396 let mut result = Vec::new();
397 decoder.read_to_end(&mut result).map_err(|e| {
398 TorshError::SerializationError(format!("Gzip decompression failed: {}", e))
399 })?;
400
401 Ok(result)
402 }
403
404 fn compress_zstd(&self, data: &[u8], level: u32) -> Result<Vec<u8>> {
406 zstd::encode_all(data, level as i32).map_err(|e| {
407 TorshError::SerializationError(format!("Zstandard compression failed: {}", e))
408 })
409 }
410
411 fn decompress_zstd(&self, data: &[u8]) -> Result<Vec<u8>> {
413 zstd::decode_all(data).map_err(|e| {
414 TorshError::SerializationError(format!("Zstandard decompression failed: {}", e))
415 })
416 }
417
418 fn compress_lzma(&self, data: &[u8], _level: u32) -> Result<Vec<u8>> {
420 let mut output = Vec::new();
421 lzma_rs::lzma_compress(&mut std::io::Cursor::new(data), &mut output).map_err(|e| {
422 TorshError::SerializationError(format!("LZMA compression failed: {}", e))
423 })?;
424
425 Ok(output)
426 }
427
428 fn decompress_lzma(&self, data: &[u8]) -> Result<Vec<u8>> {
430 let mut output = Vec::new();
431 lzma_rs::lzma_decompress(&mut std::io::Cursor::new(data), &mut output).map_err(|e| {
432 TorshError::SerializationError(format!("LZMA decompression failed: {}", e))
433 })?;
434
435 Ok(output)
436 }
437
438 fn compress_brotli(&self, data: &[u8], level: u32) -> Result<Vec<u8>> {
440 self.compress_gzip(data, level.min(9))
443 }
444
445 fn decompress_brotli(&self, data: &[u8]) -> Result<Vec<u8>> {
447 self.decompress_gzip(data)
450 }
451
452 fn compress_lz4(&self, data: &[u8], _level: u32) -> Result<Vec<u8>> {
454 self.compress_gzip(data, 1) }
458
459 fn decompress_lz4(&self, data: &[u8]) -> Result<Vec<u8>> {
461 self.decompress_gzip(data)
464 }
465}
466
467impl Default for AdvancedCompressor {
468 fn default() -> Self {
469 Self::new()
470 }
471}
472
473pub struct ParallelCompressor {
475 compressor: AdvancedCompressor,
476 chunk_size: usize,
477 num_threads: usize,
478}
479
480impl ParallelCompressor {
481 pub fn new(compressor: AdvancedCompressor) -> Self {
483 Self {
484 compressor,
485 chunk_size: 1024 * 1024, num_threads: scirs2_core::parallel_ops::num_threads(),
487 }
488 }
489
490 pub fn with_chunk_size(mut self, chunk_size: usize) -> Self {
492 self.chunk_size = chunk_size;
493 self
494 }
495
496 pub fn with_num_threads(mut self, num_threads: usize) -> Self {
498 self.num_threads = num_threads;
499 self
500 }
501
502 pub fn compress_parallel(
504 &self,
505 data: &[u8],
506 algorithm: CompressionAlgorithm,
507 level: CompressionLevel,
508 ) -> Result<CompressionResult> {
509 if data.len() < self.chunk_size * 2 {
510 return self.compressor.compress_data(data, algorithm, level);
512 }
513
514 let start_time = std::time::Instant::now();
515
516 let num_chunks = (data.len() + self.chunk_size - 1) / self.chunk_size;
518 let chunks: Vec<&[u8]> = (0..num_chunks)
519 .map(|i| {
520 let start = i * self.chunk_size;
521 let end = (start + self.chunk_size).min(data.len());
522 &data[start..end]
523 })
524 .collect();
525
526 use scirs2_core::parallel_ops::{IntoParallelIterator, ParallelIterator};
528
529 let compressed_chunks: Vec<_> = chunks
530 .into_par_iter()
531 .map(|chunk| {
532 self.compressor
533 .compress_data(chunk, algorithm, level)
534 .map(|result| result.data)
535 })
536 .collect::<Result<Vec<_>>>()?;
537
538 let mut combined_data = Vec::new();
540 combined_data.extend_from_slice(&(compressed_chunks.len() as u64).to_le_bytes());
541
542 for chunk in &compressed_chunks {
543 combined_data.extend_from_slice(&(chunk.len() as u64).to_le_bytes());
544 combined_data.extend_from_slice(chunk);
545 }
546
547 let compression_time_ms = start_time.elapsed().as_millis() as u64;
548 let compressed_size = combined_data.len();
549 let ratio = if data.is_empty() {
550 1.0
551 } else {
552 compressed_size as f32 / data.len() as f32
553 };
554
555 Ok(CompressionResult {
556 data: combined_data,
557 algorithm,
558 level,
559 original_size: data.len(),
560 compressed_size,
561 ratio,
562 compression_time_ms,
563 })
564 }
565
566 pub fn decompress_parallel(
568 &self,
569 compressed_data: &[u8],
570 algorithm: CompressionAlgorithm,
571 ) -> Result<DecompressionResult> {
572 if compressed_data.len() < 8 {
573 return self.compressor.decompress_data(compressed_data, algorithm);
575 }
576
577 let start_time = std::time::Instant::now();
578
579 let num_chunks = u64::from_le_bytes(
581 compressed_data[0..8]
582 .try_into()
583 .expect("slice of 8 bytes should convert to [u8; 8]"),
584 ) as usize;
585 let mut offset = 8;
586
587 let mut chunks = Vec::with_capacity(num_chunks);
589 for _ in 0..num_chunks {
590 if offset + 8 > compressed_data.len() {
591 return Err(TorshError::InvalidArgument(
592 "Invalid parallel-compressed data format".to_string(),
593 ));
594 }
595
596 let chunk_size = u64::from_le_bytes(
597 compressed_data[offset..offset + 8]
598 .try_into()
599 .expect("slice of 8 bytes should convert to [u8; 8]"),
600 ) as usize;
601 offset += 8;
602
603 if offset + chunk_size > compressed_data.len() {
604 return Err(TorshError::InvalidArgument(
605 "Invalid chunk size in parallel-compressed data".to_string(),
606 ));
607 }
608
609 chunks.push(&compressed_data[offset..offset + chunk_size]);
610 offset += chunk_size;
611 }
612
613 use scirs2_core::parallel_ops::{IntoParallelIterator, ParallelIterator};
615
616 let decompressed_chunks: Vec<_> = chunks
617 .into_par_iter()
618 .map(|chunk| {
619 self.compressor
620 .decompress_data(chunk, algorithm)
621 .map(|result| result.data)
622 })
623 .collect::<Result<Vec<_>>>()?;
624
625 let combined_data = decompressed_chunks.into_iter().flatten().collect();
627
628 let decompression_time_ms = start_time.elapsed().as_millis() as u64;
629
630 Ok(DecompressionResult {
631 data: combined_data,
632 algorithm,
633 decompression_time_ms,
634 })
635 }
636}
637
638#[derive(Debug, Clone, Default)]
640pub struct CompressionStats {
641 pub total_compressed: usize,
643 pub total_after_compression: usize,
645 pub total_time_ms: u64,
647 pub algorithm_usage: HashMap<CompressionAlgorithm, u32>,
649 pub algorithm_ratios: HashMap<CompressionAlgorithm, f32>,
651}
652
653impl CompressionStats {
654 pub fn new() -> Self {
656 Self::default()
657 }
658
659 pub fn record(&mut self, result: &CompressionResult) {
661 self.total_compressed += result.original_size;
662 self.total_after_compression += result.compressed_size;
663 self.total_time_ms += result.compression_time_ms;
664
665 *self.algorithm_usage.entry(result.algorithm).or_insert(0) += 1;
666
667 let current_ratio = self.algorithm_ratios.get(&result.algorithm).unwrap_or(&0.0);
669 let count = self.algorithm_usage[&result.algorithm] as f32;
670 let new_ratio = (current_ratio * (count - 1.0) + result.ratio) / count;
671 self.algorithm_ratios.insert(result.algorithm, new_ratio);
672 }
673
674 pub fn overall_ratio(&self) -> f32 {
676 if self.total_compressed == 0 {
677 1.0
678 } else {
679 self.total_after_compression as f32 / self.total_compressed as f32
680 }
681 }
682
683 pub fn space_saved(&self) -> usize {
685 self.total_compressed
686 .saturating_sub(self.total_after_compression)
687 }
688
689 pub fn space_saved_percent(&self) -> f32 {
691 if self.total_compressed == 0 {
692 0.0
693 } else {
694 (self.space_saved() as f32 / self.total_compressed as f32) * 100.0
695 }
696 }
697
698 pub fn most_used_algorithm(&self) -> Option<CompressionAlgorithm> {
700 self.algorithm_usage
701 .iter()
702 .max_by_key(|(_, &count)| count)
703 .map(|(&algorithm, _)| algorithm)
704 }
705
706 pub fn best_performing_algorithm(&self) -> Option<CompressionAlgorithm> {
708 self.algorithm_ratios
709 .iter()
710 .min_by(|(_, &a), (_, &b)| a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal))
711 .map(|(&algorithm, _)| algorithm)
712 }
713}
714
715#[cfg(test)]
716mod tests {
717 use super::*;
718
719 #[test]
720 fn test_compression_config() {
721 let config = CompressionConfig::new()
722 .with_algorithm(CompressionAlgorithm::Zstd)
723 .with_level(CompressionLevel(8))
724 .with_strategy(CompressionStrategy::Size);
725
726 assert_eq!(config.default_algorithm, CompressionAlgorithm::Zstd);
727 assert_eq!(config.default_level.0, 8);
728 assert_eq!(config.strategy, CompressionStrategy::Size);
729 }
730
731 #[test]
732 fn test_compression_level() {
733 let level = CompressionLevel(15);
734
735 assert_eq!(level.for_algorithm(CompressionAlgorithm::Gzip), 9);
736 assert_eq!(level.for_algorithm(CompressionAlgorithm::Zstd), 15);
737 assert_eq!(level.for_algorithm(CompressionAlgorithm::Brotli), 11);
738 }
739
740 #[test]
741 fn test_basic_compression() {
742 let compressor = AdvancedCompressor::new();
743 let test_data = "Hello, World! ".repeat(100);
744
745 let result = compressor
746 .compress_data(
747 test_data.as_bytes(),
748 CompressionAlgorithm::Gzip,
749 CompressionLevel(6),
750 )
751 .unwrap();
752
753 assert_eq!(result.algorithm, CompressionAlgorithm::Gzip);
754 assert_eq!(result.original_size, test_data.len());
755 assert!(result.compressed_size < result.original_size);
756 assert!(result.ratio < 1.0);
757 }
758
759 #[test]
760 fn test_decompression() {
761 let compressor = AdvancedCompressor::new();
762 let test_data = "This is test data for compression and decompression.".repeat(10);
763
764 let compression_result = compressor
765 .compress_data(
766 test_data.as_bytes(),
767 CompressionAlgorithm::Gzip,
768 CompressionLevel(6),
769 )
770 .unwrap();
771
772 let decompression_result = compressor
773 .decompress_data(&compression_result.data, CompressionAlgorithm::Gzip)
774 .unwrap();
775
776 assert_eq!(decompression_result.data, test_data.as_bytes());
777 assert_eq!(decompression_result.algorithm, CompressionAlgorithm::Gzip);
778 }
779
780 #[test]
781 fn test_resource_compression() {
782 let compressor = AdvancedCompressor::new();
783
784 let resource = Resource::new(
785 "test.txt".to_string(),
786 ResourceType::Text,
787 "This is a test text file with some content that should compress well."
788 .repeat(20)
789 .as_bytes()
790 .to_vec(),
791 );
792
793 let result = compressor.compress_resource(&resource).unwrap();
794
795 assert!(result.ratio < 0.5);
797 assert_eq!(result.original_size, resource.data.len());
798 }
799
800 #[test]
801 fn test_compression_stats() {
802 let mut stats = CompressionStats::new();
803
804 let result1 = CompressionResult {
805 data: vec![0; 100],
806 algorithm: CompressionAlgorithm::Gzip,
807 level: CompressionLevel(6),
808 original_size: 200,
809 compressed_size: 100,
810 ratio: 0.5,
811 compression_time_ms: 10,
812 };
813
814 let result2 = CompressionResult {
815 data: vec![0; 80],
816 algorithm: CompressionAlgorithm::Zstd,
817 level: CompressionLevel(6),
818 original_size: 200,
819 compressed_size: 80,
820 ratio: 0.4,
821 compression_time_ms: 8,
822 };
823
824 stats.record(&result1);
825 stats.record(&result2);
826
827 assert_eq!(stats.total_compressed, 400);
828 assert_eq!(stats.total_after_compression, 180);
829 assert_eq!(stats.space_saved(), 220);
830 assert!((stats.space_saved_percent() - 55.0).abs() < 0.1);
831
832 assert_eq!(stats.algorithm_usage[&CompressionAlgorithm::Gzip], 1);
833 assert_eq!(stats.algorithm_usage[&CompressionAlgorithm::Zstd], 1);
834
835 assert_eq!(
836 stats.best_performing_algorithm(),
837 Some(CompressionAlgorithm::Zstd)
838 );
839 }
840
841 #[test]
842 fn test_small_file_skip() {
843 let compressor = AdvancedCompressor::new();
844 let small_data = b"tiny";
845
846 let small_resource = Resource::new(
848 "small.txt".to_string(),
849 ResourceType::Text,
850 small_data.to_vec(),
851 );
852
853 let result = compressor.compress_resource(&small_resource).unwrap();
854
855 assert_eq!(result.algorithm, CompressionAlgorithm::None);
857 assert_eq!(result.data, small_data);
858 assert_eq!(result.ratio, 1.0);
859 }
860
861 #[test]
862 fn test_benchmark_algorithms() {
863 let compressor = AdvancedCompressor::new();
864 let test_data = "This is benchmark data. ".repeat(100);
865
866 let results = compressor
867 .benchmark_algorithms(test_data.as_bytes())
868 .unwrap();
869
870 assert!(results.len() >= 2);
872
873 for i in 1..results.len() {
875 assert!(results[i - 1].ratio <= results[i].ratio);
876 }
877
878 for result in &results {
880 assert_eq!(result.original_size, test_data.len());
881 }
882 }
883}