• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vortex-data / vortex / 16992591828

15 Aug 2025 02:51PM UTC coverage: 87.203% (-0.5%) from 87.72%
16992591828

Pull #2456

github

web-flow
Merge fe7e226a7 into 4a23f65b3
Pull Request #2456: feat: basic BoolBuffer / BoolBufferMut

476 of 1230 new or added lines in 107 files covered. (38.7%)

74 existing lines in 19 files now uncovered.

56525 of 64820 relevant lines covered (87.2%)

623751.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.93
/vortex-array/src/compute/conformance/filter.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use vortex_dtype::DType;
5
use vortex_error::VortexUnwrap;
6
use vortex_mask::Mask;
7

8
use crate::arrays::BoolArray;
9
use crate::compute::filter;
10
use crate::{Array, IntoArray};
11

12
// Standard test array sizes
13
pub const SMALL_SIZE: usize = 5;
14
pub const MEDIUM_SIZE: usize = 100;
15
pub const LARGE_SIZE: usize = 1024;
16

17
/// Test filter compute function with various array sizes and patterns.
18
/// The input array can be of any length.
19
pub fn test_filter_conformance(array: &dyn Array) {
1,290✔
20
    let len = array.len();
1,290✔
21

22
    // Test with arrays of any size
23
    if len > 0 {
1,290✔
24
        test_all_filter(array);
1,290✔
25
        test_none_filter(array);
1,290✔
26
        test_selective_filter(array);
1,290✔
27
        test_single_element_filter(array);
1,290✔
28
        test_nullable_filter(array);
1,290✔
29
        test_alternating_pattern_filter(array);
1,290✔
30
        test_runs_pattern_filter(array);
1,290✔
31
        test_sparse_true_filter(array);
1,290✔
32
        test_sparse_false_filter(array);
1,290✔
33
    }
1,290✔
34

35
    // Test random pattern for arrays with at least 4 elements
36
    if len >= 4 {
1,290✔
37
        test_random_pattern_filter(array);
1,244✔
38
    }
1,244✔
39

40
    // Test edge cases
41
    test_empty_array_filter(array.dtype());
1,290✔
42
    test_mismatched_lengths(array);
1,290✔
43

44
    // Test with nullable masks
45
    if len > 0 {
1,290✔
46
        test_nullable_mask_input(array);
1,290✔
47
    }
1,290✔
48
}
1,290✔
49

50
// Helper functions for creating standard patterns
51
pub fn create_alternating_pattern(len: usize) -> Vec<bool> {
1,290✔
52
    (0..len).map(|i| i % 2 == 0).collect()
9,210✔
53
}
1,290✔
54

55
pub fn create_sparse_pattern(len: usize, true_ratio: f64) -> Vec<bool> {
92✔
56
    (0..len)
92✔
57
        .map(|i| (i as f64 / len as f64) < true_ratio)
6,336✔
58
        .collect()
92✔
59
}
92✔
60

61
pub fn create_runs_pattern(len: usize, run_length: usize) -> Vec<bool> {
1,244✔
62
    (0..len).map(|i| (i / run_length) % 2 == 0).collect()
9,162✔
63
}
1,244✔
64

65
/// Tests that filtering with an all-true mask returns all elements unchanged
66
fn test_all_filter(array: &dyn Array) {
1,290✔
67
    let len = array.len();
1,290✔
68
    let mask = Mask::new_true(len);
1,290✔
69
    let filtered = filter(array, &mask).vortex_unwrap();
1,290✔
70
    assert_eq!(filtered.len(), len);
1,290✔
71

72
    // Verify all elements are preserved
73
    for i in 0..len {
9,210✔
74
        assert_eq!(
9,210✔
75
            filtered.scalar_at(i).vortex_unwrap(),
9,210✔
76
            array.scalar_at(i).vortex_unwrap()
9,210✔
77
        );
78
    }
79
}
1,290✔
80

81
/// Tests that filtering with an all-false mask returns an empty array with the same dtype
82
fn test_none_filter(array: &dyn Array) {
1,290✔
83
    let len = array.len();
1,290✔
84
    let mask = Mask::new_false(len);
1,290✔
85
    let filtered = filter(array, &mask).vortex_unwrap();
1,290✔
86
    assert_eq!(filtered.len(), 0);
1,290✔
87
    assert_eq!(filtered.dtype(), array.dtype());
1,290✔
88
}
1,290✔
89

90
fn test_selective_filter(array: &dyn Array) {
1,290✔
91
    let len = array.len();
1,290✔
92
    if len < 2 {
1,290✔
93
        return; // Skip for very small arrays
44✔
94
    }
1,246✔
95

96
    // Test alternating pattern
97
    let mask_values: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
9,166✔
98
    let expected_count = mask_values.iter().filter(|&&v| v).count();
1,246✔
99
    let mask = Mask::try_from(&BoolArray::from_iter(mask_values)).vortex_unwrap();
1,246✔
100
    let filtered = filter(array, &mask).vortex_unwrap();
1,246✔
101
    assert_eq!(filtered.len(), expected_count);
1,246✔
102

103
    // Verify correct elements are kept
104
    for (filtered_idx, i) in (0..len).step_by(2).enumerate() {
5,181✔
105
        assert_eq!(
5,181✔
106
            filtered.scalar_at(filtered_idx).vortex_unwrap(),
5,181✔
107
            array.scalar_at(i).vortex_unwrap()
5,181✔
108
        );
109
    }
110

111
    // Test first and last only
112
    if len >= 2 {
1,246✔
113
        let mut mask_values = vec![false; len];
1,246✔
114
        mask_values[0] = true;
1,246✔
115
        mask_values[len - 1] = true;
1,246✔
116
        let mask = Mask::try_from(&BoolArray::from_iter(mask_values)).vortex_unwrap();
1,246✔
117
        let filtered = filter(array, &mask).vortex_unwrap();
1,246✔
118
        assert_eq!(filtered.len(), 2);
1,246✔
119
        assert_eq!(
1,246✔
120
            filtered.scalar_at(0).vortex_unwrap(),
1,246✔
121
            array.scalar_at(0).vortex_unwrap()
1,246✔
122
        );
123
        assert_eq!(
1,246✔
124
            filtered.scalar_at(1).vortex_unwrap(),
1,246✔
125
            array.scalar_at(len - 1).vortex_unwrap()
1,246✔
126
        );
UNCOV
127
    }
×
128
}
1,290✔
129

130
fn test_single_element_filter(array: &dyn Array) {
1,290✔
131
    let len = array.len();
1,290✔
132
    if len == 0 {
1,290✔
UNCOV
133
        return;
×
134
    }
1,290✔
135

136
    // Test selecting only the first element
137
    let mut mask_values = vec![false; len];
1,290✔
138
    mask_values[0] = true;
1,290✔
139
    let mask = Mask::try_from(&BoolArray::from_iter(mask_values)).vortex_unwrap();
1,290✔
140
    let filtered = filter(array, &mask).vortex_unwrap();
1,290✔
141
    assert_eq!(filtered.len(), 1);
1,290✔
142
    assert_eq!(
1,290✔
143
        filtered.scalar_at(0).vortex_unwrap(),
1,290✔
144
        array.scalar_at(0).vortex_unwrap()
1,290✔
145
    );
146

147
    // Test selecting only the last element
148
    if len > 1 {
1,290✔
149
        let mut mask_values = vec![false; len];
1,246✔
150
        mask_values[len - 1] = true;
1,246✔
151
        let mask = Mask::try_from(&BoolArray::from_iter(mask_values)).vortex_unwrap();
1,246✔
152
        let filtered = filter(array, &mask).vortex_unwrap();
1,246✔
153
        assert_eq!(filtered.len(), 1);
1,246✔
154
        assert_eq!(
1,246✔
155
            filtered.scalar_at(0).vortex_unwrap(),
1,246✔
156
            array.scalar_at(len - 1).vortex_unwrap()
1,246✔
157
        );
158
    }
44✔
159
}
1,290✔
160

161
fn test_nullable_filter(array: &dyn Array) {
1,290✔
162
    let len = array.len();
1,290✔
163
    if len < 2 {
1,290✔
164
        return; // Skip for very small arrays
44✔
165
    }
1,246✔
166

167
    // Create a nullable mask where nulls are treated as false
168
    let bool_values: Vec<bool> = (0..len).map(|i| i % 3 == 0).collect();
9,166✔
169
    let validity_values: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
9,166✔
170

171
    let expected_count = bool_values
1,246✔
172
        .iter()
1,246✔
173
        .zip(validity_values.iter())
1,246✔
174
        .filter(|(b, v)| **b && **v)
9,166✔
175
        .count();
1,246✔
176

177
    let bool_array = BoolArray::from_iter(bool_values.clone());
1,246✔
178
    let validity = crate::validity::Validity::from_iter(validity_values.clone());
1,246✔
179
    let nullable_mask = BoolArray::new(bool_array.boolean_buffer().clone(), validity);
1,246✔
180

181
    let mask = Mask::try_from(&nullable_mask).vortex_unwrap();
1,246✔
182
    let filtered = filter(array, &mask).vortex_unwrap();
1,246✔
183
    assert_eq!(filtered.len(), expected_count);
1,246✔
184

185
    // Verify correct elements are kept
186
    let mut filtered_idx = 0;
1,246✔
187
    for i in 0..len {
9,166✔
188
        if bool_values[i] && validity_values[i] {
9,166✔
189
            assert_eq!(
1,757✔
190
                filtered.scalar_at(filtered_idx).vortex_unwrap(),
1,757✔
191
                array.scalar_at(i).vortex_unwrap()
1,757✔
192
            );
193
            filtered_idx += 1;
1,757✔
194
        }
7,409✔
195
    }
196
}
1,290✔
197

198
fn test_empty_array_filter(dtype: &DType) {
1,290✔
199
    use crate::Canonical;
200

201
    let empty_array = Canonical::empty(dtype).into_array();
1,290✔
202
    let empty_mask = Mask::new_false(0);
1,290✔
203
    let filtered = filter(&empty_array, &empty_mask).vortex_unwrap();
1,290✔
204
    assert_eq!(filtered.len(), 0);
1,290✔
205

206
    let empty_mask = Mask::new_true(0);
1,290✔
207
    let filtered = filter(&empty_array, &empty_mask).vortex_unwrap();
1,290✔
208
    assert_eq!(filtered.len(), 0);
1,290✔
209
}
1,290✔
210

211
fn test_mismatched_lengths(array: &dyn Array) {
1,290✔
212
    let len = array.len();
1,290✔
213

214
    // Test mask shorter than array
215
    if len > 0 {
1,290✔
216
        let short_mask = Mask::new_true(len - 1);
1,290✔
217
        let result = filter(array, &short_mask);
1,290✔
218
        assert!(
1,290✔
219
            result.is_err(),
1,290✔
UNCOV
220
            "Filter should fail with mismatched lengths"
×
221
        );
UNCOV
222
    }
×
223

224
    // Test mask longer than array
225
    let long_mask = Mask::new_true(len + 1);
1,290✔
226
    let result = filter(array, &long_mask);
1,290✔
227
    assert!(
1,290✔
228
        result.is_err(),
1,290✔
UNCOV
229
        "Filter should fail with mismatched lengths"
×
230
    );
231
}
1,290✔
232

233
/// Tests filtering with alternating true/false pattern
234
fn test_alternating_pattern_filter(array: &dyn Array) {
1,290✔
235
    let len = array.len();
1,290✔
236
    let pattern = create_alternating_pattern(len);
1,290✔
237
    let expected_count = pattern.iter().filter(|&&v| v).count();
1,290✔
238

239
    let mask = Mask::try_from(&BoolArray::from_iter(pattern.clone())).vortex_unwrap();
1,290✔
240
    let filtered = filter(array, &mask).vortex_unwrap();
1,290✔
241
    assert_eq!(filtered.len(), expected_count);
1,290✔
242

243
    // Verify correct elements are kept
244
    let mut filtered_idx = 0;
1,290✔
245
    for (i, &keep) in pattern.iter().enumerate() {
9,210✔
246
        if keep {
9,210✔
247
            assert_eq!(
5,225✔
248
                filtered.scalar_at(filtered_idx).vortex_unwrap(),
5,225✔
249
                array.scalar_at(i).vortex_unwrap()
5,225✔
250
            );
251
            filtered_idx += 1;
5,225✔
252
        }
3,985✔
253
    }
254
}
1,290✔
255

256
/// Tests filtering with runs of true/false values
257
fn test_runs_pattern_filter(array: &dyn Array) {
1,290✔
258
    let len = array.len();
1,290✔
259
    if len < 4 {
1,290✔
260
        return; // Skip for very small arrays
46✔
261
    }
1,244✔
262

263
    let run_length = len.min(3);
1,244✔
264
    let pattern = create_runs_pattern(len, run_length);
1,244✔
265
    let expected_count = pattern.iter().filter(|&&v| v).count();
1,244✔
266

267
    let mask = Mask::try_from(&BoolArray::from_iter(pattern)).vortex_unwrap();
1,244✔
268
    let filtered = filter(array, &mask).vortex_unwrap();
1,244✔
269
    assert_eq!(filtered.len(), expected_count);
1,244✔
270
}
1,290✔
271

272
/// Tests filtering with sparse true values (mostly false)
273
fn test_sparse_true_filter(array: &dyn Array) {
1,290✔
274
    let len = array.len();
1,290✔
275
    if len < 10 {
1,290✔
276
        return; // Skip for small arrays
1,244✔
277
    }
46✔
278

279
    // Only keep about 10% of values
280
    let pattern = create_sparse_pattern(len, 0.1);
46✔
281
    let expected_count = pattern.iter().filter(|&&v| v).count();
46✔
282

283
    let mask = Mask::try_from(&BoolArray::from_iter(pattern)).vortex_unwrap();
46✔
284
    let filtered = filter(array, &mask).vortex_unwrap();
46✔
285
    assert_eq!(filtered.len(), expected_count);
46✔
286
}
1,290✔
287

288
/// Tests filtering with sparse false values (mostly true)
289
fn test_sparse_false_filter(array: &dyn Array) {
1,290✔
290
    let len = array.len();
1,290✔
291
    if len < 10 {
1,290✔
292
        return; // Skip for small arrays
1,244✔
293
    }
46✔
294

295
    // Keep about 90% of values
296
    let pattern = create_sparse_pattern(len, 0.9);
46✔
297
    let expected_count = pattern.iter().filter(|&&v| v).count();
46✔
298

299
    let mask = Mask::try_from(&BoolArray::from_iter(pattern)).vortex_unwrap();
46✔
300
    let filtered = filter(array, &mask).vortex_unwrap();
46✔
301
    assert_eq!(filtered.len(), expected_count);
46✔
302
}
1,290✔
303

304
/// Tests filtering with random pattern
305
fn test_random_pattern_filter(array: &dyn Array) {
1,244✔
306
    let len = array.len();
1,244✔
307

308
    // Create a pseudo-random pattern based on array length
309
    let pattern: Vec<bool> = (0..len)
1,244✔
310
        .map(|i| ((i * 37 + 17) % 5) < 3) // Deterministic pseudo-random
9,162✔
311
        .collect();
1,244✔
312
    let expected_count = pattern.iter().filter(|&&v| v).count();
1,244✔
313

314
    let mask = Mask::try_from(&BoolArray::from_iter(pattern)).vortex_unwrap();
1,244✔
315
    let filtered = filter(array, &mask).vortex_unwrap();
1,244✔
316
    assert_eq!(filtered.len(), expected_count);
1,244✔
317
}
1,244✔
318

319
/// Tests filtering with nullable mask (nulls treated as false)
320
fn test_nullable_mask_input(array: &dyn Array) {
1,290✔
321
    let len = array.len();
1,290✔
322
    if len < 3 {
1,290✔
323
        return; // Skip for very small arrays
46✔
324
    }
1,244✔
325

326
    // Create a nullable mask where every third value is null
327
    let bool_values: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
9,162✔
328
    let validity_values: Vec<bool> = (0..len).map(|i| i % 3 != 0).collect();
9,162✔
329

330
    // Only values that are true AND valid should pass the filter
331
    let expected_count = bool_values
1,244✔
332
        .iter()
1,244✔
333
        .zip(validity_values.iter())
1,244✔
334
        .filter(|(b, v)| **b && **v)
9,162✔
335
        .count();
1,244✔
336

337
    let bool_array = BoolArray::from_iter(bool_values.clone());
1,244✔
338
    let validity = crate::validity::Validity::from_iter(validity_values.clone());
1,244✔
339
    let nullable_mask = BoolArray::new(bool_array.boolean_buffer().clone(), validity);
1,244✔
340

341
    let mask = Mask::try_from(&nullable_mask).vortex_unwrap();
1,244✔
342
    let filtered = filter(array, &mask).vortex_unwrap();
1,244✔
343
    assert_eq!(filtered.len(), expected_count);
1,244✔
344

345
    // Verify correct elements are kept
346
    let mut filtered_idx = 0;
1,244✔
347
    for i in 0..len {
9,162✔
348
        if bool_values[i] && validity_values[i] {
9,162✔
349
            assert_eq!(
3,424✔
350
                filtered.scalar_at(filtered_idx).vortex_unwrap(),
3,424✔
351
                array.scalar_at(i).vortex_unwrap()
3,424✔
352
            );
353
            filtered_idx += 1;
3,424✔
354
        }
5,738✔
355
    }
356
}
1,290✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc