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

vortex-data / vortex / 16605708121

29 Jul 2025 07:37PM UTC coverage: 83.004% (+0.3%) from 82.684%
16605708121

Pull #4055

github

web-flow
Merge d4d61ec22 into 6fb0f3e49
Pull Request #4055: [chore] cross-operation consistency tests

355 of 360 new or added lines in 22 files covered. (98.61%)

27 existing lines in 2 files now uncovered.

45688 of 55043 relevant lines covered (83.0%)

353098.6 hits per line

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

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

4
//! Tests for consistency between related compute operations
5

6
use vortex_error::VortexUnwrap;
7
use vortex_mask::Mask;
8

9
use crate::arrays::{BoolArray, PrimitiveArray};
10
use crate::compute::{filter, mask, take};
11
use crate::{Array, IntoArray};
12

13
/// Tests that filter and take operations produce consistent results
14
/// filter(array, mask) should equal take(array, indices_where_mask_is_true)
15
pub fn test_filter_take_consistency(array: &dyn Array) {
3,651✔
16
    let len = array.len();
3,651✔
17
    if len == 0 {
3,651✔
18
        return;
1✔
19
    }
3,650✔
20

21
    // Create a test mask
22
    let mask_pattern: Vec<bool> = (0..len).map(|i| i % 3 != 1).collect();
1,373,272✔
23
    let mask = Mask::try_from(&BoolArray::from_iter(mask_pattern.clone())).vortex_unwrap();
3,650✔
24

25
    // Filter the array
26
    let filtered = filter(array, &mask).vortex_unwrap();
3,650✔
27

28
    // Create indices where mask is true
29
    let indices: Vec<u64> = mask_pattern
3,650✔
30
        .iter()
3,650✔
31
        .enumerate()
3,650✔
32
        .filter_map(|(i, &v)| v.then_some(i as u64))
1,373,272✔
33
        .collect();
3,650✔
34
    let indices_array = PrimitiveArray::from_iter(indices).into_array();
3,650✔
35

36
    // Take using those indices
37
    let taken = take(array, &indices_array).vortex_unwrap();
3,650✔
38

39
    // Results should be identical
40
    assert_eq!(filtered.len(), taken.len());
3,650✔
41
    for i in 0..filtered.len() {
915,065✔
42
        assert_eq!(
915,065✔
43
            filtered.scalar_at(i).vortex_unwrap(),
915,065✔
44
            taken.scalar_at(i).vortex_unwrap()
915,065✔
45
        );
46
    }
47
}
3,651✔
48

49
/// Tests that double masking is consistent with combined mask
50
/// mask(mask(array, mask1), mask2) should equal mask(array, mask1 | mask2)
51
pub fn test_double_mask_consistency(array: &dyn Array) {
3,651✔
52
    let len = array.len();
3,651✔
53
    if len == 0 {
3,651✔
54
        return;
1✔
55
    }
3,650✔
56

57
    // Create two different mask patterns
58
    let mask1_pattern: Vec<bool> = (0..len).map(|i| i % 3 == 0).collect();
1,373,272✔
59
    let mask2_pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
1,373,272✔
60

61
    let mask1 = Mask::try_from(&BoolArray::from_iter(mask1_pattern.clone())).vortex_unwrap();
3,650✔
62
    let mask2 = Mask::try_from(&BoolArray::from_iter(mask2_pattern.clone())).vortex_unwrap();
3,650✔
63

64
    // Apply masks sequentially
65
    let first_masked = mask(array, &mask1).vortex_unwrap();
3,650✔
66
    let double_masked = mask(&first_masked, &mask2).vortex_unwrap();
3,650✔
67

68
    // Create combined mask (OR operation)
69
    let combined_pattern: Vec<bool> = mask1_pattern
3,650✔
70
        .iter()
3,650✔
71
        .zip(mask2_pattern.iter())
3,650✔
72
        .map(|(&a, &b)| a || b)
1,373,272✔
73
        .collect();
3,650✔
74
    let combined_mask = Mask::try_from(&BoolArray::from_iter(combined_pattern)).vortex_unwrap();
3,650✔
75

76
    // Apply combined mask directly
77
    let directly_masked = mask(array, &combined_mask).vortex_unwrap();
3,650✔
78

79
    // Results should be identical
80
    assert_eq!(double_masked.len(), directly_masked.len());
3,650✔
81
    for i in 0..double_masked.len() {
1,373,272✔
82
        assert_eq!(
1,373,272✔
83
            double_masked.scalar_at(i).vortex_unwrap(),
1,373,272✔
84
            directly_masked.scalar_at(i).vortex_unwrap()
1,373,272✔
85
        );
86
    }
87
}
3,651✔
88

89
/// Tests consistency when filtering with all-true mask vs no operation
90
pub fn test_filter_identity(array: &dyn Array) {
3,651✔
91
    let len = array.len();
3,651✔
92
    if len == 0 {
3,651✔
93
        return;
1✔
94
    }
3,650✔
95

96
    let all_true_mask = Mask::new_true(len);
3,650✔
97
    let filtered = filter(array, &all_true_mask).vortex_unwrap();
3,650✔
98

99
    // Filtered array should be identical to original
100
    assert_eq!(filtered.len(), array.len());
3,650✔
101
    for i in 0..len {
1,373,272✔
102
        assert_eq!(
1,373,272✔
103
            filtered.scalar_at(i).vortex_unwrap(),
1,373,272✔
104
            array.scalar_at(i).vortex_unwrap()
1,373,272✔
105
        );
106
    }
107
}
3,651✔
108

109
/// Tests consistency when masking with all-false mask vs no masking
110
pub fn test_mask_identity(array: &dyn Array) {
3,651✔
111
    let len = array.len();
3,651✔
112
    if len == 0 {
3,651✔
113
        return;
1✔
114
    }
3,650✔
115

116
    let all_false_mask = Mask::new_false(len);
3,650✔
117
    let masked = mask(array, &all_false_mask).vortex_unwrap();
3,650✔
118

119
    // Masked array should have same values (just nullable)
120
    assert_eq!(masked.len(), array.len());
3,650✔
121
    for i in 0..len {
1,373,272✔
122
        assert_eq!(
1,373,272✔
123
            masked.scalar_at(i).vortex_unwrap(),
1,373,272✔
124
            array.scalar_at(i).vortex_unwrap().into_nullable()
1,373,272✔
125
        );
126
    }
127
}
3,651✔
128

129
/// Tests that slice and filter with contiguous mask produce same results
130
pub fn test_slice_filter_consistency(array: &dyn Array) {
3,651✔
131
    let len = array.len();
3,651✔
132
    if len < 4 {
3,651✔
133
        return;
587✔
134
    }
3,064✔
135

136
    // Create a contiguous mask (true from index 1 to 3)
137
    let mut mask_pattern = vec![false; len];
3,064✔
138
    mask_pattern[1..4.min(len)].fill(true);
3,064✔
139

140
    let mask = Mask::try_from(&BoolArray::from_iter(mask_pattern)).vortex_unwrap();
3,064✔
141
    let filtered = filter(array, &mask).vortex_unwrap();
3,064✔
142

143
    // Slice should produce the same result
144
    let sliced = array.slice(1, 4.min(len)).vortex_unwrap();
3,064✔
145

146
    assert_eq!(filtered.len(), sliced.len());
3,064✔
147
    for i in 0..filtered.len() {
9,192✔
148
        assert_eq!(
9,192✔
149
            filtered.scalar_at(i).vortex_unwrap(),
9,192✔
150
            sliced.scalar_at(i).vortex_unwrap()
9,192✔
151
        );
152
    }
153
}
3,651✔
154

155
/// Tests that take with sequential indices equals slice
156
pub fn test_take_slice_consistency(array: &dyn Array) {
3,651✔
157
    let len = array.len();
3,651✔
158
    if len < 3 {
3,651✔
159
        return;
433✔
160
    }
3,218✔
161

162
    // Take indices [1, 2, 3]
163
    let end = 4.min(len);
3,218✔
164
    let indices = PrimitiveArray::from_iter((1..end).map(|i| i as u64)).into_array();
9,500✔
165
    let taken = take(array, &indices).vortex_unwrap();
3,218✔
166

167
    // Slice from 1 to end
168
    let sliced = array.slice(1, end).vortex_unwrap();
3,218✔
169

170
    assert_eq!(taken.len(), sliced.len());
3,218✔
171
    for i in 0..taken.len() {
9,500✔
172
        assert_eq!(
9,500✔
173
            taken.scalar_at(i).vortex_unwrap(),
9,500✔
174
            sliced.scalar_at(i).vortex_unwrap()
9,500✔
175
        );
176
    }
177
}
3,651✔
178

179
/// Tests that filter preserves relative ordering
180
pub fn test_filter_preserves_order(array: &dyn Array) {
3,651✔
181
    let len = array.len();
3,651✔
182
    if len < 4 {
3,651✔
183
        return;
587✔
184
    }
3,064✔
185

186
    // Create a mask that selects elements at indices 0, 2, 3
187
    let mask_pattern: Vec<bool> = (0..len).map(|i| i == 0 || i == 2 || i == 3).collect();
1,372,337✔
188
    let mask = Mask::try_from(&BoolArray::from_iter(mask_pattern)).vortex_unwrap();
3,064✔
189

190
    let filtered = filter(array, &mask).vortex_unwrap();
3,064✔
191

192
    // Verify the filtered array contains the right elements in order
193
    assert_eq!(filtered.len(), 3.min(len));
3,064✔
194
    if len >= 4 {
3,064✔
195
        assert_eq!(
3,064✔
196
            filtered.scalar_at(0).vortex_unwrap(),
3,064✔
197
            array.scalar_at(0).vortex_unwrap()
3,064✔
198
        );
199
        assert_eq!(
3,064✔
200
            filtered.scalar_at(1).vortex_unwrap(),
3,064✔
201
            array.scalar_at(2).vortex_unwrap()
3,064✔
202
        );
203
        assert_eq!(
3,064✔
204
            filtered.scalar_at(2).vortex_unwrap(),
3,064✔
205
            array.scalar_at(3).vortex_unwrap()
3,064✔
206
        );
UNCOV
207
    }
×
208
}
3,651✔
209

210
/// Tests that take with repeated indices works correctly
211
pub fn test_take_repeated_indices(array: &dyn Array) {
3,651✔
212
    let len = array.len();
3,651✔
213
    if len == 0 {
3,651✔
214
        return;
1✔
215
    }
3,650✔
216

217
    // Take the first element three times
218
    let indices = PrimitiveArray::from_iter([0u64, 0, 0]).into_array();
3,650✔
219
    let taken = take(array, &indices).vortex_unwrap();
3,650✔
220

221
    assert_eq!(taken.len(), 3);
3,650✔
222
    for i in 0..3 {
14,600✔
223
        assert_eq!(
10,950✔
224
            taken.scalar_at(i).vortex_unwrap(),
10,950✔
225
            array.scalar_at(0).vortex_unwrap()
10,950✔
226
        );
227
    }
228
}
3,651✔
229

230
/// Tests mask and filter interaction with nulls
231
pub fn test_mask_filter_null_consistency(array: &dyn Array) {
3,651✔
232
    let len = array.len();
3,651✔
233
    if len < 3 {
3,651✔
234
        return;
433✔
235
    }
3,218✔
236

237
    // First mask some elements
238
    let mask_pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
1,372,799✔
239
    let mask_array = Mask::try_from(&BoolArray::from_iter(mask_pattern)).vortex_unwrap();
3,218✔
240
    let masked = mask(array, &mask_array).vortex_unwrap();
3,218✔
241

242
    // Then filter to remove the nulls
243
    let filter_pattern: Vec<bool> = (0..len).map(|i| i % 2 != 0).collect();
1,372,799✔
244
    let filter_mask = Mask::try_from(&BoolArray::from_iter(filter_pattern)).vortex_unwrap();
3,218✔
245
    let filtered = filter(&masked, &filter_mask).vortex_unwrap();
3,218✔
246

247
    // This should be equivalent to directly filtering the original array
248
    let direct_filtered = filter(array, &filter_mask).vortex_unwrap();
3,218✔
249

250
    assert_eq!(filtered.len(), direct_filtered.len());
3,218✔
251
    for i in 0..filtered.len() {
685,334✔
252
        assert_eq!(
685,334✔
253
            filtered.scalar_at(i).vortex_unwrap(),
685,334✔
254
            direct_filtered.scalar_at(i).vortex_unwrap()
685,334✔
255
        );
256
    }
257
}
3,651✔
258

259
/// Tests that empty operations are consistent
260
pub fn test_empty_operations_consistency(array: &dyn Array) {
3,651✔
261
    let len = array.len();
3,651✔
262

263
    // Empty filter
264
    let empty_filter = filter(array, &Mask::new_false(len)).vortex_unwrap();
3,651✔
265
    assert_eq!(empty_filter.len(), 0);
3,651✔
266
    assert_eq!(empty_filter.dtype(), array.dtype());
3,651✔
267

268
    // Empty take
269
    let empty_indices =
3,651✔
270
        PrimitiveArray::empty::<u64>(vortex_dtype::Nullability::NonNullable).into_array();
3,651✔
271
    let empty_take = take(array, &empty_indices).vortex_unwrap();
3,651✔
272
    assert_eq!(empty_take.len(), 0);
3,651✔
273
    assert_eq!(empty_take.dtype(), array.dtype());
3,651✔
274

275
    // Empty slice (if array is non-empty)
276
    if len > 0 {
3,651✔
277
        let empty_slice = array.slice(0, 0).vortex_unwrap();
3,650✔
278
        assert_eq!(empty_slice.len(), 0);
3,650✔
279
        assert_eq!(empty_slice.dtype(), array.dtype());
3,650✔
280
    }
1✔
281
}
3,651✔
282

283
/// Tests that take preserves array properties
284
pub fn test_take_preserves_properties(array: &dyn Array) {
3,651✔
285
    let len = array.len();
3,651✔
286
    if len == 0 {
3,651✔
287
        return;
1✔
288
    }
3,650✔
289

290
    // Take all elements in original order
291
    let indices = PrimitiveArray::from_iter((0..len).map(|i| i as u64)).into_array();
1,373,272✔
292
    let taken = take(array, &indices).vortex_unwrap();
3,650✔
293

294
    // Should be identical to original
295
    assert_eq!(taken.len(), array.len());
3,650✔
296
    assert_eq!(taken.dtype(), array.dtype());
3,650✔
297
    for i in 0..len {
1,373,272✔
298
        assert_eq!(
1,373,272✔
299
            taken.scalar_at(i).vortex_unwrap(),
1,373,272✔
300
            array.scalar_at(i).vortex_unwrap()
1,373,272✔
301
        );
302
    }
303
}
3,651✔
304

305
/// Tests consistency with nullable indices
306
pub fn test_nullable_indices_consistency(array: &dyn Array) {
3,651✔
307
    let len = array.len();
3,651✔
308
    if len < 3 {
3,651✔
309
        return;
433✔
310
    }
3,218✔
311

312
    // Create nullable indices where some indices are null
313
    let indices = PrimitiveArray::from_option_iter([Some(0u64), None, Some(2u64)]).into_array();
3,218✔
314

315
    let taken = take(array, &indices).vortex_unwrap();
3,218✔
316

317
    // Result should have nulls where indices were null
318
    assert_eq!(taken.len(), 3);
3,218✔
319
    assert!(taken.dtype().is_nullable());
3,218✔
320
    assert_eq!(
3,218✔
321
        taken.scalar_at(0).vortex_unwrap(),
3,218✔
322
        array.scalar_at(0).vortex_unwrap().into_nullable()
3,218✔
323
    );
324
    assert!(taken.scalar_at(1).vortex_unwrap().is_null());
3,218✔
325
    assert_eq!(
3,218✔
326
        taken.scalar_at(2).vortex_unwrap(),
3,218✔
327
        array.scalar_at(2).vortex_unwrap().into_nullable()
3,218✔
328
    );
329
}
3,651✔
330

331
/// Tests large array consistency
332
pub fn test_large_array_consistency(array: &dyn Array) {
3,651✔
333
    let len = array.len();
3,651✔
334
    if len < 1000 {
3,651✔
335
        return;
2,959✔
336
    }
692✔
337

338
    // Test with every 10th element
339
    let indices: Vec<u64> = (0..len).step_by(10).map(|i| i as u64).collect();
135,540✔
340
    let indices_array = PrimitiveArray::from_iter(indices).into_array();
692✔
341
    let taken = take(array, &indices_array).vortex_unwrap();
692✔
342

343
    // Create equivalent filter mask
344
    let mask_pattern: Vec<bool> = (0..len).map(|i| i % 10 == 0).collect();
1,354,944✔
345
    let mask = Mask::try_from(&BoolArray::from_iter(mask_pattern)).vortex_unwrap();
692✔
346
    let filtered = filter(array, &mask).vortex_unwrap();
692✔
347

348
    // Results should match
349
    assert_eq!(taken.len(), filtered.len());
692✔
350
    for i in 0..taken.len() {
135,540✔
351
        assert_eq!(
135,540✔
352
            taken.scalar_at(i).vortex_unwrap(),
135,540✔
353
            filtered.scalar_at(i).vortex_unwrap()
135,540✔
354
        );
355
    }
356
}
3,651✔
357

358
/// Run all consistency tests on an array
359
pub fn test_array_consistency(array: &dyn Array) {
3,651✔
360
    test_filter_take_consistency(array);
3,651✔
361
    test_double_mask_consistency(array);
3,651✔
362
    test_filter_identity(array);
3,651✔
363
    test_mask_identity(array);
3,651✔
364
    test_slice_filter_consistency(array);
3,651✔
365
    test_take_slice_consistency(array);
3,651✔
366
    test_filter_preserves_order(array);
3,651✔
367
    test_take_repeated_indices(array);
3,651✔
368
    test_mask_filter_null_consistency(array);
3,651✔
369
    test_empty_operations_consistency(array);
3,651✔
370
    test_take_preserves_properties(array);
3,651✔
371
    test_nullable_indices_consistency(array);
3,651✔
372
    test_large_array_consistency(array);
3,651✔
373
}
3,651✔
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