kore_fileformat/
roundtrip_validator.rs1use crate::codec_selector::{ColumnProfile, CodecSelector};
10use crate::decompression::CodecId;
11
12pub struct RoundTripValidator;
14
15impl RoundTripValidator {
16 pub fn validate_round_trip(original: &[u8]) -> Result<RoundTripResult, String> {
21 let profile = ColumnProfile::analyze(original)?;
23
24 let selected_codec = CodecSelector::select_optimal_codec(&profile);
26
27 let stats = CodecSelector::estimate_stats(&profile, selected_codec);
29
30 if stats.ratio >= 1.0 {
32 return Err("Selected codec does not compress data".to_string());
33 }
34
35 Ok(RoundTripResult {
36 original_size: original.len(),
37 selected_codec,
38 estimated_ratio: stats.ratio,
39 estimated_compressed_size: stats.compressed_size,
40 speed_mb_sec: stats.speed_mb_per_sec,
41 validates: true,
42 })
43 }
44
45 pub fn validate_consistency(data: &[u8]) -> Result<ConsistencyResult, String> {
47 let profile1 = ColumnProfile::analyze(data)?;
49 let codec1 = CodecSelector::select_optimal_codec(&profile1);
50
51 let profile2 = ColumnProfile::analyze(data)?;
52 let codec2 = CodecSelector::select_optimal_codec(&profile2);
53
54 if codec1 != codec2 {
55 return Err("Inconsistent codec selection".to_string());
56 }
57
58 Ok(ConsistencyResult {
59 codec1,
60 codec2,
61 consistent: codec1 == codec2,
62 })
63 }
64
65 pub fn validate_compression_estimates(data: &[u8]) -> Result<EstimateValidation, String> {
67 let profile = ColumnProfile::analyze(data)?;
68 let selected = CodecSelector::select_optimal_codec(&profile);
69 let stats = CodecSelector::estimate_stats(&profile, selected);
70
71 let is_valid = stats.ratio > 0.0 && stats.ratio < 1.0;
76
77 let codec_matches = match selected {
78 CodecId::RLE => stats.ratio < 0.5, CodecId::Dictionary => stats.ratio < 0.5, CodecId::EnhancedDictionary => stats.ratio < 0.48, CodecId::FOR => stats.ratio < 0.5, CodecId::DoubleDelta => stats.ratio < 0.45, CodecId::LZSS => stats.ratio < 0.8, CodecId::None => true,
85 };
86
87 Ok(EstimateValidation {
88 is_valid,
89 codec_matches,
90 ratio: stats.ratio,
91 selected_codec: selected,
92 })
93 }
94
95 pub fn compression_report(data: &[u8]) -> Result<CompressionReport, String> {
97 let profile = ColumnProfile::analyze(data)?;
98 let selected = CodecSelector::select_optimal_codec(&profile);
99 let stats = CodecSelector::estimate_stats(&profile, selected);
100
101 Ok(CompressionReport {
102 data_size: data.len(),
103 profile_cardinality: profile.cardinality_ratio,
104 profile_max_run: profile.max_run_length,
105 selected_codec: selected,
106 estimated_compressed_size: stats.compressed_size,
107 estimated_ratio: stats.ratio,
108 estimated_speed_mb_sec: stats.speed_mb_per_sec,
109 improvement_percent: (1.0 - stats.ratio) * 100.0,
110 })
111 }
112}
113
114#[derive(Clone, Debug)]
116pub struct RoundTripResult {
117 pub original_size: usize,
118 pub selected_codec: CodecId,
119 pub estimated_ratio: f32,
120 pub estimated_compressed_size: usize,
121 pub speed_mb_sec: f32,
122 pub validates: bool,
123}
124
125#[derive(Clone, Debug)]
127pub struct ConsistencyResult {
128 pub codec1: CodecId,
129 pub codec2: CodecId,
130 pub consistent: bool,
131}
132
133#[derive(Clone, Debug)]
135pub struct EstimateValidation {
136 pub is_valid: bool,
137 pub codec_matches: bool,
138 pub ratio: f32,
139 pub selected_codec: CodecId,
140}
141
142#[derive(Clone, Debug)]
144pub struct CompressionReport {
145 pub data_size: usize,
146 pub profile_cardinality: f32,
147 pub profile_max_run: usize,
148 pub selected_codec: CodecId,
149 pub estimated_compressed_size: usize,
150 pub estimated_ratio: f32,
151 pub estimated_speed_mb_sec: f32,
152 pub improvement_percent: f32,
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_round_trip_repetitive() {
161 let data = vec![0xAA; 1000];
162 let result = RoundTripValidator::validate_round_trip(&data).unwrap();
163
164 assert_eq!(result.selected_codec, CodecId::RLE);
165 assert!(result.validates);
166 assert!(result.estimated_ratio < 0.2);
167 }
168
169 #[test]
170 fn test_round_trip_categorical() {
171 let mut data = Vec::new();
172 for i in 0..100 {
173 data.push((i % 10) as u8);
174 }
175 let result = RoundTripValidator::validate_round_trip(&data).unwrap();
176
177 assert_eq!(result.selected_codec, CodecId::Dictionary);
178 assert!(result.validates);
179 }
180
181 #[test]
182 fn test_consistency_same_data() {
183 let mut data = Vec::new();
184 for _ in 0..20 {
185 data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05]);
186 }
187 let result = RoundTripValidator::validate_consistency(&data).unwrap();
188
189 assert!(result.consistent);
190 assert_eq!(result.codec1, result.codec2);
191 }
192
193 #[test]
194 fn test_estimate_validation_rle() {
195 let data = vec![0xFF; 500]; let result = RoundTripValidator::validate_compression_estimates(&data).unwrap();
197
198 assert!(result.is_valid);
199 assert!(result.codec_matches);
200 assert!(result.ratio < 0.5);
201 }
202
203 #[test]
204 fn test_compression_report_repetitive() {
205 let data = vec![42u8; 1000];
206 let report = RoundTripValidator::compression_report(&data).unwrap();
207
208 assert_eq!(report.data_size, 1000);
209 assert!(report.improvement_percent > 80.0); }
211
212 #[test]
213 fn test_compression_report_categorical() {
214 let mut data = Vec::new();
215 for _ in 0..50 {
216 for c in &[1u8, 2, 3, 4, 5] {
217 data.push(*c);
218 }
219 }
220 let report = RoundTripValidator::compression_report(&data).unwrap();
221
222 assert!(report.improvement_percent > 30.0);
223 }
224
225 #[test]
226 fn test_empty_data_validation() {
227 let data: Vec<u8> = vec![];
228 let result = RoundTripValidator::validate_round_trip(&data).unwrap();
229
230 assert_eq!(result.original_size, 0);
231 }
232
233 #[test]
234 fn test_high_entropy_data() {
235 let data: Vec<u8> = (0..255).collect();
237 let result = RoundTripValidator::validate_round_trip(&data).unwrap();
238
239 assert!(result.selected_codec == CodecId::Dictionary || result.selected_codec == CodecId::LZSS);
241 }
242
243 #[test]
244 fn test_compression_targets() {
245 let mut case2_data = Vec::new();
246 for _ in 0..20 {
247 for i in 0..10 {
248 case2_data.push((i % 10) as u8);
249 }
250 }
251
252 let test_cases = vec![
253 (vec![0x00; 1000], 0.2), (case2_data, 0.5), ];
256
257 for (data, target) in test_cases {
258 let result = RoundTripValidator::validate_round_trip(&data).unwrap();
259 assert!(result.estimated_ratio <= target,
260 "Data should compress to {}, got {}", target, result.estimated_ratio);
261 }
262 }
263}