|
4 | 4 | #![no_main] |
5 | 5 | #![allow(clippy::unwrap_used, clippy::result_large_err)] |
6 | 6 |
|
7 | | -use std::backtrace::Backtrace; |
8 | | - |
9 | 7 | use libfuzzer_sys::Corpus; |
10 | 8 | use libfuzzer_sys::fuzz_target; |
11 | 9 | use vortex_array::Array; |
12 | 10 | use vortex_array::ArrayRef; |
13 | | -use vortex_array::IntoArray; |
14 | | -use vortex_array::arrays::ConstantArray; |
15 | | -use vortex_array::compute::MinMaxResult; |
16 | | -use vortex_array::compute::cast; |
17 | | -use vortex_array::compute::compare; |
18 | | -use vortex_array::compute::fill_null; |
19 | | -use vortex_array::compute::filter; |
20 | | -use vortex_array::compute::mask; |
21 | | -use vortex_array::compute::min_max; |
22 | | -use vortex_array::compute::sum; |
23 | | -use vortex_array::compute::take; |
24 | | -use vortex_array::search_sorted::SearchResult; |
25 | | -use vortex_array::search_sorted::SearchSorted; |
26 | | -use vortex_array::search_sorted::SearchSortedSide; |
27 | 11 | use vortex_btrblocks::BtrBlocksCompressor; |
28 | 12 | use vortex_error::VortexUnwrap; |
29 | 13 | use vortex_error::vortex_panic; |
30 | | -use vortex_fuzz::Action; |
31 | | -use vortex_fuzz::CompressorStrategy; |
32 | 14 | use vortex_fuzz::FuzzArrayAction; |
33 | | -use vortex_fuzz::error::VortexFuzzError; |
34 | | -use vortex_fuzz::error::VortexFuzzResult; |
35 | | -use vortex_fuzz::sort_canonical_array; |
| 15 | +use vortex_fuzz::FuzzCompressor; |
| 16 | +use vortex_fuzz::run_fuzz_action; |
36 | 17 | use vortex_layout::layouts::compact::CompactCompressor; |
37 | | -use vortex_scalar::Scalar; |
38 | 18 |
|
39 | | -fuzz_target!(|fuzz_action: FuzzArrayAction| -> Corpus { |
40 | | - let FuzzArrayAction { array, actions } = fuzz_action; |
41 | | - let mut current_array = array.to_array(); |
42 | | - for (i, (action, expected)) in actions.into_iter().enumerate() { |
43 | | - match action { |
44 | | - Action::Compress(strategy) => { |
45 | | - current_array = match strategy { |
46 | | - CompressorStrategy::Default => BtrBlocksCompressor::default() |
47 | | - .compress(current_array.to_canonical().as_ref()) |
48 | | - .vortex_unwrap(), |
49 | | - CompressorStrategy::Compact => CompactCompressor::default() |
50 | | - .compress(current_array.to_canonical().as_ref()) |
51 | | - .vortex_unwrap(), |
52 | | - }; |
53 | | - assert_array_eq(&expected.array(), ¤t_array, i).unwrap(); |
54 | | - } |
55 | | - Action::Slice(range) => { |
56 | | - current_array = current_array.slice(range); |
57 | | - assert_array_eq(&expected.array(), ¤t_array, i).unwrap(); |
58 | | - } |
59 | | - Action::Take(indices) => { |
60 | | - if indices.is_empty() { |
61 | | - return Corpus::Reject; |
62 | | - } |
63 | | - current_array = take(¤t_array, &indices).vortex_unwrap(); |
64 | | - assert_array_eq(&expected.array(), ¤t_array, i).unwrap(); |
65 | | - } |
66 | | - Action::SearchSorted(s, side) => { |
67 | | - // TODO(robert): Ideally we'd preserve the encoding perfectly but this is close enough |
68 | | - let mut sorted = sort_canonical_array(¤t_array).vortex_unwrap(); |
| 19 | +/// Native compressor that supports both BtrBlocks and Compact strategies. |
| 20 | +struct NativeCompressor; |
69 | 21 |
|
70 | | - // If the current array is not in one of these canonical encodings, compress again. |
71 | | - if !current_array.is_canonical() { |
72 | | - sorted = BtrBlocksCompressor::default() |
73 | | - .compress(&sorted) |
74 | | - .vortex_unwrap(); |
75 | | - } |
76 | | - assert_search_sorted(sorted, s, side, expected.search(), i).unwrap() |
77 | | - } |
78 | | - Action::Filter(mask) => { |
79 | | - current_array = filter(¤t_array, &mask).vortex_unwrap(); |
80 | | - assert_array_eq(&expected.array(), ¤t_array, i).unwrap(); |
81 | | - } |
82 | | - Action::Compare(v, op) => { |
83 | | - let compare_result = compare( |
84 | | - ¤t_array, |
85 | | - &ConstantArray::new(v.clone(), current_array.len()).into_array(), |
86 | | - op, |
87 | | - ) |
88 | | - .vortex_unwrap(); |
89 | | - if let Err(e) = assert_array_eq(&expected.array(), &compare_result, i) { |
90 | | - vortex_panic!( |
91 | | - "Failed to compare {}with {op} {v}\nError: {e}", |
92 | | - current_array.display_tree() |
93 | | - ) |
94 | | - } |
95 | | - current_array = compare_result; |
96 | | - } |
97 | | - Action::Cast(to) => { |
98 | | - let cast_result = cast(¤t_array, &to).vortex_unwrap(); |
99 | | - if let Err(e) = assert_array_eq(&expected.array(), &cast_result, i) { |
100 | | - vortex_panic!( |
101 | | - "Failed to cast {} to dtype {to}\nError: {e}", |
102 | | - current_array.display_tree() |
103 | | - ) |
104 | | - } |
105 | | - current_array = cast_result; |
106 | | - } |
107 | | - Action::Sum => { |
108 | | - let sum_result = sum(¤t_array).vortex_unwrap(); |
109 | | - assert_scalar_eq(&expected.scalar(), &sum_result, i).unwrap(); |
110 | | - } |
111 | | - Action::MinMax => { |
112 | | - let min_max_result = min_max(¤t_array).vortex_unwrap(); |
113 | | - assert_min_max_eq(&expected.min_max(), &min_max_result, i).unwrap(); |
114 | | - } |
115 | | - Action::FillNull(fill_value) => { |
116 | | - current_array = fill_null(¤t_array, &fill_value).vortex_unwrap(); |
117 | | - assert_array_eq(&expected.array(), ¤t_array, i).unwrap(); |
118 | | - } |
119 | | - Action::Mask(mask_val) => { |
120 | | - current_array = mask(¤t_array, &mask_val).vortex_unwrap(); |
121 | | - assert_array_eq(&expected.array(), ¤t_array, i).unwrap(); |
122 | | - } |
123 | | - Action::ScalarAt(indices) => { |
124 | | - let expected_scalars = expected.scalar_vec(); |
125 | | - for (j, &idx) in indices.iter().enumerate() { |
126 | | - let scalar = current_array.scalar_at(idx); |
127 | | - assert_scalar_eq(&expected_scalars[j], &scalar, i).unwrap(); |
128 | | - } |
129 | | - } |
130 | | - } |
| 22 | +impl FuzzCompressor for NativeCompressor { |
| 23 | + fn compress_default(&self, array: &dyn Array) -> ArrayRef { |
| 24 | + BtrBlocksCompressor::default() |
| 25 | + .compress(array) |
| 26 | + .vortex_unwrap() |
131 | 27 | } |
132 | | - Corpus::Keep |
133 | | -}); |
134 | 28 |
|
135 | | -fn assert_search_sorted( |
136 | | - array: ArrayRef, |
137 | | - s: Scalar, |
138 | | - side: SearchSortedSide, |
139 | | - expected: SearchResult, |
140 | | - step: usize, |
141 | | -) -> VortexFuzzResult<()> { |
142 | | - let search_result = array.search_sorted(&s, side); |
143 | | - if search_result != expected { |
144 | | - Err(VortexFuzzError::SearchSortedError( |
145 | | - s, |
146 | | - expected, |
147 | | - array.to_array(), |
148 | | - side, |
149 | | - search_result, |
150 | | - step, |
151 | | - Backtrace::capture(), |
152 | | - )) |
153 | | - } else { |
154 | | - Ok(()) |
| 29 | + fn compress_compact(&self, array: &dyn Array) -> ArrayRef { |
| 30 | + CompactCompressor::default().compress(array).vortex_unwrap() |
155 | 31 | } |
156 | 32 | } |
157 | 33 |
|
158 | | -fn assert_array_eq(lhs: &ArrayRef, rhs: &ArrayRef, step: usize) -> VortexFuzzResult<()> { |
159 | | - if lhs.dtype() != rhs.dtype() { |
160 | | - return Err(VortexFuzzError::DTypeMismatch( |
161 | | - lhs.clone(), |
162 | | - rhs.clone(), |
163 | | - step, |
164 | | - Backtrace::capture(), |
165 | | - )); |
166 | | - } |
167 | | - |
168 | | - if lhs.len() != rhs.len() { |
169 | | - return Err(VortexFuzzError::LengthMismatch( |
170 | | - lhs.len(), |
171 | | - rhs.len(), |
172 | | - lhs.to_array(), |
173 | | - rhs.to_array(), |
174 | | - step, |
175 | | - Backtrace::capture(), |
176 | | - )); |
177 | | - } |
178 | | - for idx in 0..lhs.len() { |
179 | | - let l = lhs.scalar_at(idx); |
180 | | - let r = rhs.scalar_at(idx); |
181 | | - |
182 | | - if l != r { |
183 | | - return Err(VortexFuzzError::ArrayNotEqual( |
184 | | - l, |
185 | | - r, |
186 | | - idx, |
187 | | - lhs.clone(), |
188 | | - rhs.clone(), |
189 | | - step, |
190 | | - Backtrace::capture(), |
191 | | - )); |
| 34 | +fuzz_target!(|fuzz_action: FuzzArrayAction| -> Corpus { |
| 35 | + match run_fuzz_action(fuzz_action, &NativeCompressor) { |
| 36 | + Ok(true) => Corpus::Keep, |
| 37 | + Ok(false) => Corpus::Reject, |
| 38 | + Err(e) => { |
| 39 | + vortex_panic!("{e}"); |
192 | 40 | } |
193 | 41 | } |
194 | | - Ok(()) |
195 | | -} |
196 | | - |
197 | | -fn assert_scalar_eq(lhs: &Scalar, rhs: &Scalar, step: usize) -> VortexFuzzResult<()> { |
198 | | - if lhs != rhs { |
199 | | - return Err(VortexFuzzError::ScalarMismatch( |
200 | | - lhs.clone(), |
201 | | - rhs.clone(), |
202 | | - step, |
203 | | - Backtrace::capture(), |
204 | | - )); |
205 | | - } |
206 | | - Ok(()) |
207 | | -} |
208 | | - |
209 | | -fn assert_min_max_eq( |
210 | | - lhs: &Option<MinMaxResult>, |
211 | | - rhs: &Option<MinMaxResult>, |
212 | | - step: usize, |
213 | | -) -> VortexFuzzResult<()> { |
214 | | - if lhs != rhs { |
215 | | - return Err(VortexFuzzError::MinMaxMismatch( |
216 | | - lhs.clone(), |
217 | | - rhs.clone(), |
218 | | - step, |
219 | | - Backtrace::capture(), |
220 | | - )); |
221 | | - } |
222 | | - Ok(()) |
223 | | -} |
| 42 | +}); |
0 commit comments