Skip to main content

vortex_array/compute/conformance/
consistency.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! # Array Consistency Tests
5//!
6//! This module contains tests that verify consistency between related compute operations
7//! on Vortex arrays. These tests ensure that different ways of achieving the same result
8//! produce identical outputs.
9//!
10//! ## Test Categories
11//!
12//! - **Filter/Take Consistency**: Verifies that filtering with a mask produces the same
13//!   result as taking with the indices where the mask is true.
14//! - **Mask Composition**: Ensures that applying multiple masks sequentially produces
15//!   the same result as applying a combined mask.
16//! - **Identity Operations**: Tests that operations with identity inputs (all-true masks,
17//!   sequential indices) preserve the original array.
18//! - **Null Handling**: Verifies consistent behavior when operations introduce or
19//!   interact with null values.
20//! - **Edge Cases**: Tests empty arrays, single elements, and boundary conditions.
21
22use std::sync::Arc;
23
24use vortex_buffer::BitBuffer;
25use vortex_error::VortexExpect;
26use vortex_error::vortex_panic;
27use vortex_mask::Mask;
28
29use crate::ArrayRef;
30use crate::IntoArray;
31use crate::LEGACY_SESSION;
32use crate::VortexSessionExecute;
33use crate::arrays::BoolArray;
34use crate::arrays::PrimitiveArray;
35use crate::builtins::ArrayBuiltins;
36use crate::dtype::DType;
37use crate::dtype::Nullability;
38use crate::dtype::PType;
39use crate::scalar_fn::fns::operators::Operator;
40
41/// Tests that filter and take operations produce consistent results.
42///
43/// # Invariant
44/// `filter(array, mask)` should equal `take(array, indices_where_mask_is_true)`
45///
46/// # Test Details
47/// - Creates a mask that keeps elements where index % 3 != 1
48/// - Applies filter with this mask
49/// - Creates indices array containing positions where mask is true
50/// - Applies take with these indices
51/// - Verifies both results are identical
52fn test_filter_take_consistency(array: &ArrayRef) {
53    let len = array.len();
54    if len == 0 {
55        return;
56    }
57
58    // Create a test mask (keep elements where index % 3 != 1)
59    let mask_pattern: BitBuffer = (0..len).map(|i| i % 3 != 1).collect();
60    let mask = Mask::from_buffer(mask_pattern.clone());
61
62    // Filter the array
63    let filtered = array
64        .filter(mask)
65        .vortex_expect("filter should succeed in conformance test");
66
67    // Create indices where mask is true
68    let indices: Vec<u64> = mask_pattern
69        .iter()
70        .enumerate()
71        .filter_map(|(i, v)| v.then_some(i as u64))
72        .collect();
73    let indices_array = PrimitiveArray::from_iter(indices).into_array();
74
75    // Take using those indices
76    let taken = array
77        .take(indices_array)
78        .vortex_expect("take should succeed in conformance test");
79
80    // Results should be identical
81    assert_eq!(
82        filtered.len(),
83        taken.len(),
84        "Filter and take should produce arrays of the same length. \
85         Filtered length: {}, Taken length: {}",
86        filtered.len(),
87        taken.len()
88    );
89
90    for i in 0..filtered.len() {
91        let filtered_val = filtered
92            .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
93            .vortex_expect("scalar_at should succeed in conformance test");
94        let taken_val = taken
95            .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
96            .vortex_expect("scalar_at should succeed in conformance test");
97        assert_eq!(
98            filtered_val, taken_val,
99            "Filter and take produced different values at index {i}. \
100             Filtered value: {filtered_val:?}, Taken value: {taken_val:?}"
101        );
102    }
103}
104
105/// Tests that double masking is consistent with combined mask.
106///
107/// # Invariant
108/// `mask(mask(array, mask1), mask2)` should equal `mask(array, mask1 | mask2)`
109///
110/// # Test Details
111/// - Creates two masks: mask1 (every 3rd element) and mask2 (every 2nd element)
112/// - Applies masks sequentially: first mask1, then mask2 on the result
113/// - Creates a combined mask using OR operation (element is masked if either mask is true)
114/// - Applies the combined mask directly to the original array
115/// - Verifies both approaches produce identical results
116///
117/// # Why This Matters
118/// This test ensures that mask operations compose correctly, which is critical for
119/// complex query operations that may apply multiple filters.
120fn test_double_mask_consistency(array: &ArrayRef) {
121    let len = array.len();
122    if len == 0 {
123        return;
124    }
125
126    // Create two different mask patterns
127    let mask1: Mask = (0..len).map(|i| i % 3 == 0).collect();
128    let mask2: Mask = (0..len).map(|i| i % 2 == 0).collect();
129
130    // Apply masks sequentially
131    let first_masked = array
132        .clone()
133        .mask((!&mask1).into_array())
134        .vortex_expect("mask should succeed in conformance test");
135    let double_masked = first_masked
136        .mask((!&mask2).into_array())
137        .vortex_expect("mask should succeed in conformance test");
138
139    // Create combined mask (OR operation - element is masked if EITHER mask is true)
140    let combined_pattern: BitBuffer = mask1
141        .to_bit_buffer()
142        .iter()
143        .zip(mask2.to_bit_buffer().iter())
144        .map(|(a, b)| a || b)
145        .collect();
146    let combined_mask = Mask::from_buffer(combined_pattern);
147
148    // Apply combined mask directly
149    let directly_masked = array
150        .clone()
151        .mask((!&combined_mask).into_array())
152        .vortex_expect("mask should succeed in conformance test");
153
154    // Results should be identical
155    assert_eq!(
156        double_masked.len(),
157        directly_masked.len(),
158        "Sequential masking and combined masking should produce arrays of the same length. \
159         Sequential length: {}, Combined length: {}",
160        double_masked.len(),
161        directly_masked.len()
162    );
163
164    for i in 0..double_masked.len() {
165        let double_val = double_masked
166            .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
167            .vortex_expect("scalar_at should succeed in conformance test");
168        let direct_val = directly_masked
169            .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
170            .vortex_expect("scalar_at should succeed in conformance test");
171        assert_eq!(
172            double_val, direct_val,
173            "Sequential masking and combined masking produced different values at index {i}. \
174             Sequential masking value: {double_val:?}, Combined masking value: {direct_val:?}\n\
175             This likely indicates an issue with how masks are composed in the array implementation."
176        );
177    }
178}
179
180/// Tests that filtering with an all-true mask preserves the array.
181///
182/// # Invariant
183/// `filter(array, all_true_mask)` should equal `array`
184///
185/// # Test Details
186/// - Creates a mask with all elements set to true
187/// - Applies filter with this mask
188/// - Verifies the result is identical to the original array
189///
190/// # Why This Matters
191/// This is an identity operation that should be optimized in implementations
192/// to avoid unnecessary copying.
193fn test_filter_identity(array: &ArrayRef) {
194    let len = array.len();
195    if len == 0 {
196        return;
197    }
198
199    let all_true_mask = Mask::new_true(len);
200    let filtered = array
201        .filter(all_true_mask)
202        .vortex_expect("filter should succeed in conformance test");
203
204    // Filtered array should be identical to original
205    assert_eq!(
206        filtered.len(),
207        array.len(),
208        "Filtering with all-true mask should preserve array length. \
209         Original length: {}, Filtered length: {}",
210        array.len(),
211        filtered.len()
212    );
213
214    for i in 0..len {
215        let original_val = array
216            .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
217            .vortex_expect("scalar_at should succeed in conformance test");
218        let filtered_val = filtered
219            .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
220            .vortex_expect("scalar_at should succeed in conformance test");
221        assert_eq!(
222            filtered_val, original_val,
223            "Filtering with all-true mask should preserve all values. \
224             Value at index {i} changed from {original_val:?} to {filtered_val:?}"
225        );
226    }
227}
228
229/// Tests that masking with an all-false mask preserves values while making them nullable.
230///
231/// # Invariant
232/// `mask(array, all_false_mask)` should have same values as `array` but with nullable type
233///
234/// # Test Details
235/// - Creates a mask with all elements set to false (no elements are nullified)
236/// - Applies mask operation
237/// - Verifies all values are preserved but the array type becomes nullable
238///
239/// # Why This Matters
240/// Masking always produces a nullable array, even when no values are actually masked.
241/// This test ensures the type system handles this correctly.
242fn test_mask_identity(array: &ArrayRef) {
243    let len = array.len();
244    if len == 0 {
245        return;
246    }
247
248    let all_false_mask = Mask::new_false(len);
249    let masked = array
250        .clone()
251        .mask((!&all_false_mask).into_array())
252        .vortex_expect("mask should succeed in conformance test");
253
254    // Masked array should have same values (just nullable)
255    assert_eq!(
256        masked.len(),
257        array.len(),
258        "Masking with all-false mask should preserve array length. \
259         Original length: {}, Masked length: {}",
260        array.len(),
261        masked.len()
262    );
263
264    assert!(
265        masked.dtype().is_nullable(),
266        "Mask operation should always produce a nullable array, but dtype is {}",
267        masked.dtype()
268    );
269
270    for i in 0..len {
271        let original_val = array
272            .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
273            .vortex_expect("scalar_at should succeed in conformance test");
274        let masked_val = masked
275            .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
276            .vortex_expect("scalar_at should succeed in conformance test");
277        let expected_val = original_val.clone().into_nullable();
278        assert_eq!(
279            masked_val, expected_val,
280            "Masking with all-false mask should preserve values (as nullable). \
281             Value at index {i}: original = {original_val:?}, masked = {masked_val:?}, expected = {expected_val:?}"
282        );
283    }
284}
285
286/// Tests that slice and filter with contiguous mask produce same results.
287///
288/// # Invariant
289/// `filter(array, contiguous_true_mask)` should equal `slice(array, start, end)`
290///
291/// # Test Details
292/// - Creates a mask that is true only for indices 1, 2, and 3
293/// - Filters the array with this mask
294/// - Slices the array from index 1 to 4
295/// - Verifies both operations produce identical results
296///
297/// # Why This Matters
298/// When a filter mask represents a contiguous range, it should be equivalent to
299/// a slice operation. Some implementations may optimize this case.
300fn test_slice_filter_consistency(array: &ArrayRef) {
301    let len = array.len();
302    if len < 4 {
303        return; // Need at least 4 elements for meaningful test
304    }
305
306    // Create a contiguous mask (true from index 1 to 3)
307    let mut mask_pattern = vec![false; len];
308    mask_pattern[1..4.min(len)].fill(true);
309
310    let mask = Mask::from_iter(mask_pattern);
311    let filtered = array
312        .filter(mask)
313        .vortex_expect("filter should succeed in conformance test");
314
315    // Slice should produce the same result
316    let sliced = array
317        .slice(1..4.min(len))
318        .vortex_expect("slice should succeed in conformance test");
319
320    assert_eq!(
321        filtered.len(),
322        sliced.len(),
323        "Filter with contiguous mask and slice should produce same length. \
324         Filtered length: {}, Sliced length: {}",
325        filtered.len(),
326        sliced.len()
327    );
328
329    for i in 0..filtered.len() {
330        let filtered_val = filtered
331            .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
332            .vortex_expect("scalar_at should succeed in conformance test");
333        let sliced_val = sliced
334            .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
335            .vortex_expect("scalar_at should succeed in conformance test");
336        assert_eq!(
337            filtered_val, sliced_val,
338            "Filter with contiguous mask and slice produced different values at index {i}. \
339             Filtered value: {filtered_val:?}, Sliced value: {sliced_val:?}"
340        );
341    }
342}
343
344/// Tests that take with sequential indices equals slice.
345///
346/// # Invariant
347/// `take(array, [1, 2, 3, ...])` should equal `slice(array, 1, n)`
348///
349/// # Test Details
350/// - Creates indices array with sequential values [1, 2, 3]
351/// - Takes elements at these indices
352/// - Slices array from index 1 to 4
353/// - Verifies both operations produce identical results
354///
355/// # Why This Matters
356/// Sequential takes are a common pattern that can be optimized to slice operations.
357fn test_take_slice_consistency(array: &ArrayRef) {
358    let len = array.len();
359    if len < 3 {
360        return; // Need at least 3 elements
361    }
362
363    // Take indices [1, 2, 3]
364    let end = 4.min(len);
365    let indices = PrimitiveArray::from_iter((1..end).map(|i| i as u64)).into_array();
366    let taken = array
367        .take(indices)
368        .vortex_expect("take should succeed in conformance test");
369
370    // Slice from 1 to end
371    let sliced = array
372        .slice(1..end)
373        .vortex_expect("slice should succeed in conformance test");
374
375    assert_eq!(
376        taken.len(),
377        sliced.len(),
378        "Take with sequential indices and slice should produce same length. \
379         Taken length: {}, Sliced length: {}",
380        taken.len(),
381        sliced.len()
382    );
383
384    for i in 0..taken.len() {
385        let taken_val = taken
386            .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
387            .vortex_expect("scalar_at should succeed in conformance test");
388        let sliced_val = sliced
389            .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
390            .vortex_expect("scalar_at should succeed in conformance test");
391        assert_eq!(
392            taken_val, sliced_val,
393            "Take with sequential indices and slice produced different values at index {i}. \
394             Taken value: {taken_val:?}, Sliced value: {sliced_val:?}"
395        );
396    }
397}
398
399/// Tests that filter preserves relative ordering
400fn test_filter_preserves_order(array: &ArrayRef) {
401    let len = array.len();
402    if len < 4 {
403        return;
404    }
405
406    // Create a mask that selects elements at indices 0, 2, 3
407    let mask_pattern: Vec<bool> = (0..len).map(|i| i == 0 || i == 2 || i == 3).collect();
408    let mask = Mask::from_iter(mask_pattern);
409
410    let filtered = array
411        .filter(mask)
412        .vortex_expect("filter should succeed in conformance test");
413
414    // Verify the filtered array contains the right elements in order
415    assert_eq!(filtered.len(), 3.min(len));
416    if len >= 4 {
417        assert_eq!(
418            filtered
419                .execute_scalar(0, &mut LEGACY_SESSION.create_execution_ctx())
420                .vortex_expect("scalar_at should succeed in conformance test"),
421            array
422                .execute_scalar(0, &mut LEGACY_SESSION.create_execution_ctx())
423                .vortex_expect("scalar_at should succeed in conformance test")
424        );
425        assert_eq!(
426            filtered
427                .execute_scalar(1, &mut LEGACY_SESSION.create_execution_ctx())
428                .vortex_expect("scalar_at should succeed in conformance test"),
429            array
430                .execute_scalar(2, &mut LEGACY_SESSION.create_execution_ctx())
431                .vortex_expect("scalar_at should succeed in conformance test")
432        );
433        assert_eq!(
434            filtered
435                .execute_scalar(2, &mut LEGACY_SESSION.create_execution_ctx())
436                .vortex_expect("scalar_at should succeed in conformance test"),
437            array
438                .execute_scalar(3, &mut LEGACY_SESSION.create_execution_ctx())
439                .vortex_expect("scalar_at should succeed in conformance test")
440        );
441    }
442}
443
444/// Tests that take with repeated indices works correctly
445fn test_take_repeated_indices(array: &ArrayRef) {
446    let len = array.len();
447    if len == 0 {
448        return;
449    }
450
451    // Take the first element three times
452    let indices = PrimitiveArray::from_iter([0u64, 0, 0]).into_array();
453    let taken = array
454        .take(indices)
455        .vortex_expect("take should succeed in conformance test");
456
457    assert_eq!(taken.len(), 3);
458    for i in 0..3 {
459        assert_eq!(
460            taken
461                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
462                .vortex_expect("scalar_at should succeed in conformance test"),
463            array
464                .execute_scalar(0, &mut LEGACY_SESSION.create_execution_ctx())
465                .vortex_expect("scalar_at should succeed in conformance test")
466        );
467    }
468}
469
470/// Tests mask and filter interaction with nulls
471fn test_mask_filter_null_consistency(array: &ArrayRef) {
472    let len = array.len();
473    if len < 3 {
474        return;
475    }
476
477    // First mask some elements
478    let mask_pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
479    let mask_array = Mask::from_iter(mask_pattern);
480    let masked = array
481        .clone()
482        .mask((!&mask_array).into_array())
483        .vortex_expect("mask should succeed in conformance test");
484
485    // Then filter to remove the nulls
486    let filter_pattern: Vec<bool> = (0..len).map(|i| i % 2 != 0).collect();
487    let filter_mask = Mask::from_iter(filter_pattern);
488    let filtered = masked
489        .filter(filter_mask.clone())
490        .vortex_expect("filter should succeed in conformance test");
491
492    // This should be equivalent to directly filtering the original array
493    let direct_filtered = array
494        .filter(filter_mask)
495        .vortex_expect("filter should succeed in conformance test");
496
497    assert_eq!(filtered.len(), direct_filtered.len());
498    for i in 0..filtered.len() {
499        assert_eq!(
500            filtered
501                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
502                .vortex_expect("scalar_at should succeed in conformance test"),
503            direct_filtered
504                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
505                .vortex_expect("scalar_at should succeed in conformance test")
506        );
507    }
508}
509
510/// Tests that empty operations are consistent
511fn test_empty_operations_consistency(array: &ArrayRef) {
512    let len = array.len();
513
514    // Empty filter
515    let empty_filter = array
516        .filter(Mask::new_false(len))
517        .vortex_expect("filter should succeed in conformance test");
518    assert_eq!(empty_filter.len(), 0);
519    assert_eq!(empty_filter.dtype(), array.dtype());
520
521    // Empty take
522    let empty_indices = PrimitiveArray::empty::<u64>(Nullability::NonNullable).into_array();
523    let empty_take = array
524        .take(empty_indices)
525        .vortex_expect("take should succeed in conformance test");
526    assert_eq!(empty_take.len(), 0);
527    assert_eq!(empty_take.dtype(), array.dtype());
528
529    // Empty slice (if array is non-empty)
530    if len > 0 {
531        let empty_slice = array
532            .slice(0..0)
533            .vortex_expect("slice should succeed in conformance test");
534        assert_eq!(empty_slice.len(), 0);
535        assert_eq!(empty_slice.dtype(), array.dtype());
536    }
537}
538
539/// Tests that take preserves array properties
540fn test_take_preserves_properties(array: &ArrayRef) {
541    let len = array.len();
542    if len == 0 {
543        return;
544    }
545
546    // Take all elements in original order
547    let indices = PrimitiveArray::from_iter((0..len).map(|i| i as u64)).into_array();
548    let taken = array
549        .take(indices)
550        .vortex_expect("take should succeed in conformance test");
551
552    // Should be identical to original
553    assert_eq!(taken.len(), array.len());
554    assert_eq!(taken.dtype(), array.dtype());
555    for i in 0..len {
556        assert_eq!(
557            taken
558                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
559                .vortex_expect("scalar_at should succeed in conformance test"),
560            array
561                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
562                .vortex_expect("scalar_at should succeed in conformance test")
563        );
564    }
565}
566
567/// Tests consistency with nullable indices.
568///
569/// # Invariant
570/// `take(array, [Some(0), None, Some(2)])` should produce `[array[0], null, array[2]]`
571///
572/// # Test Details
573/// - Creates an indices array with null at position 1: `[Some(0), None, Some(2)]`
574/// - Takes elements using these indices
575/// - Verifies that:
576///   - Position 0 contains the value from array index 0
577///   - Position 1 contains null
578///   - Position 2 contains the value from array index 2
579///   - The result array has nullable type
580///
581/// # Why This Matters
582/// Nullable indices are a powerful feature that allows introducing nulls during
583/// a take operation, which is useful for outer joins and similar operations.
584fn test_nullable_indices_consistency(array: &ArrayRef) {
585    let len = array.len();
586    if len < 3 {
587        return; // Need at least 3 elements to test indices 0 and 2
588    }
589
590    // Create nullable indices where some indices are null
591    let indices = PrimitiveArray::from_option_iter([Some(0u64), None, Some(2u64)]).into_array();
592
593    let taken = array
594        .take(indices)
595        .vortex_expect("take should succeed in conformance test");
596
597    // Result should have nulls where indices were null
598    assert_eq!(
599        taken.len(),
600        3,
601        "Take with nullable indices should produce array of length 3, got {}",
602        taken.len()
603    );
604
605    assert!(
606        taken.dtype().is_nullable(),
607        "Take with nullable indices should produce nullable array, but dtype is {:?}",
608        taken.dtype()
609    );
610
611    // Check first element (from index 0)
612    let expected_0 = array
613        .execute_scalar(0, &mut LEGACY_SESSION.create_execution_ctx())
614        .vortex_expect("scalar_at should succeed in conformance test")
615        .into_nullable();
616    let actual_0 = taken
617        .execute_scalar(0, &mut LEGACY_SESSION.create_execution_ctx())
618        .vortex_expect("scalar_at should succeed in conformance test");
619    assert_eq!(
620        actual_0, expected_0,
621        "Take with nullable indices: element at position 0 should be from array index 0. \
622         Expected: {expected_0:?}, Actual: {actual_0:?}"
623    );
624
625    // Check second element (should be null)
626    let actual_1 = taken
627        .execute_scalar(1, &mut LEGACY_SESSION.create_execution_ctx())
628        .vortex_expect("scalar_at should succeed in conformance test");
629    assert!(
630        actual_1.is_null(),
631        "Take with nullable indices: element at position 1 should be null, but got {actual_1:?}"
632    );
633
634    // Check third element (from index 2)
635    let expected_2 = array
636        .execute_scalar(2, &mut LEGACY_SESSION.create_execution_ctx())
637        .vortex_expect("scalar_at should succeed in conformance test")
638        .into_nullable();
639    let actual_2 = taken
640        .execute_scalar(2, &mut LEGACY_SESSION.create_execution_ctx())
641        .vortex_expect("scalar_at should succeed in conformance test");
642    assert_eq!(
643        actual_2, expected_2,
644        "Take with nullable indices: element at position 2 should be from array index 2. \
645         Expected: {expected_2:?}, Actual: {actual_2:?}"
646    );
647}
648
649/// Tests large array consistency
650fn test_large_array_consistency(array: &ArrayRef) {
651    let len = array.len();
652    if len < 1000 {
653        return;
654    }
655
656    // Test with every 10th element
657    let indices: Vec<u64> = (0..len).step_by(10).map(|i| i as u64).collect();
658    let indices_array = PrimitiveArray::from_iter(indices).into_array();
659    let taken = array
660        .take(indices_array)
661        .vortex_expect("take should succeed in conformance test");
662
663    // Create equivalent filter mask
664    let mask_pattern: Vec<bool> = (0..len).map(|i| i % 10 == 0).collect();
665    let mask = Mask::from_iter(mask_pattern);
666    let filtered = array
667        .filter(mask)
668        .vortex_expect("filter should succeed in conformance test");
669
670    // Results should match
671    assert_eq!(taken.len(), filtered.len());
672    for i in 0..taken.len() {
673        assert_eq!(
674            taken
675                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
676                .vortex_expect("scalar_at should succeed in conformance test"),
677            filtered
678                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
679                .vortex_expect("scalar_at should succeed in conformance test")
680        );
681    }
682}
683
684/// Tests that comparison operations follow inverse relationships.
685///
686/// # Invariants
687/// - `compare(array, value, Eq)` is the inverse of `compare(array, value, NotEq)`
688/// - `compare(array, value, Gt)` is the inverse of `compare(array, value, Lte)`
689/// - `compare(array, value, Lt)` is the inverse of `compare(array, value, Gte)`
690///
691/// # Test Details
692/// - Creates comparison results for each operator
693/// - Verifies that inverse operations produce opposite boolean values
694/// - Tests with multiple scalar values to ensure consistency
695///
696/// # Why This Matters
697/// Comparison operations must maintain logical consistency across encodings.
698/// This test catches bugs where an encoding might implement one comparison
699/// correctly but fail on its logical inverse.
700fn test_comparison_inverse_consistency(array: &ArrayRef) {
701    let len = array.len();
702    if len == 0 {
703        return;
704    }
705
706    // Skip non-comparable types.
707    match array.dtype() {
708        DType::Null
709        | DType::List(..)
710        | DType::Struct(..)
711        | DType::Union(..)
712        | DType::Extension(_) => return,
713        _ => {}
714    }
715
716    // Get a test value from the middle of the array
717    let test_scalar = if len == 0 {
718        return;
719    } else {
720        array
721            .execute_scalar(len / 2, &mut LEGACY_SESSION.create_execution_ctx())
722            .vortex_expect("scalar_at should succeed in conformance test")
723    };
724
725    // Test Eq vs NotEq
726    let const_array = crate::arrays::ConstantArray::new(test_scalar, len);
727    if let (Ok(eq_result), Ok(neq_result)) = (
728        array
729            .clone()
730            .binary(const_array.clone().into_array(), Operator::Eq),
731        array
732            .clone()
733            .binary(const_array.clone().into_array(), Operator::NotEq),
734    ) {
735        let inverted_eq = eq_result
736            .not()
737            .vortex_expect("not should succeed in conformance test");
738
739        assert_eq!(
740            inverted_eq.len(),
741            neq_result.len(),
742            "Inverted Eq should have same length as NotEq"
743        );
744
745        for i in 0..inverted_eq.len() {
746            let inv_val = inverted_eq
747                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
748                .vortex_expect("scalar_at should succeed in conformance test");
749            let neq_val = neq_result
750                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
751                .vortex_expect("scalar_at should succeed in conformance test");
752            assert_eq!(
753                inv_val, neq_val,
754                "At index {i}: NOT(Eq) should equal NotEq. \
755                 NOT(Eq) = {inv_val:?}, NotEq = {neq_val:?}"
756            );
757        }
758    }
759
760    // Test Gt vs Lte
761    if let (Ok(gt_result), Ok(lte_result)) = (
762        array
763            .clone()
764            .binary(const_array.clone().into_array(), Operator::Gt),
765        array
766            .clone()
767            .binary(const_array.clone().into_array(), Operator::Lte),
768    ) {
769        let inverted_gt = gt_result
770            .not()
771            .vortex_expect("not should succeed in conformance test");
772
773        for i in 0..inverted_gt.len() {
774            let inv_val = inverted_gt
775                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
776                .vortex_expect("scalar_at should succeed in conformance test");
777            let lte_val = lte_result
778                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
779                .vortex_expect("scalar_at should succeed in conformance test");
780            assert_eq!(
781                inv_val, lte_val,
782                "At index {i}: NOT(Gt) should equal Lte. \
783                 NOT(Gt) = {inv_val:?}, Lte = {lte_val:?}"
784            );
785        }
786    }
787
788    // Test Lt vs Gte
789    if let (Ok(lt_result), Ok(gte_result)) = (
790        array
791            .clone()
792            .binary(const_array.clone().into_array(), Operator::Lt),
793        array
794            .clone()
795            .binary(const_array.into_array(), Operator::Gte),
796    ) {
797        let inverted_lt = lt_result
798            .not()
799            .vortex_expect("not should succeed in conformance test");
800
801        for i in 0..inverted_lt.len() {
802            let inv_val = inverted_lt
803                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
804                .vortex_expect("scalar_at should succeed in conformance test");
805            let gte_val = gte_result
806                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
807                .vortex_expect("scalar_at should succeed in conformance test");
808            assert_eq!(
809                inv_val, gte_val,
810                "At index {i}: NOT(Lt) should equal Gte. \
811                 NOT(Lt) = {inv_val:?}, Gte = {gte_val:?}"
812            );
813        }
814    }
815}
816
817/// Tests that comparison operations maintain proper symmetry relationships.
818///
819/// # Invariants
820/// - `compare(array, value, Gt)` should equal `compare_scalar_array(value, array, Lt)`
821/// - `compare(array, value, Lt)` should equal `compare_scalar_array(value, array, Gt)`
822/// - `compare(array, value, Eq)` should equal `compare_scalar_array(value, array, Eq)`
823///
824/// # Test Details
825/// - Compares array-scalar operations with their symmetric scalar-array versions
826/// - Verifies that ordering relationships are properly reversed
827/// - Tests equality which should be symmetric
828///
829/// # Why This Matters
830/// Ensures that comparison operations maintain mathematical ordering properties
831/// regardless of operand order.
832fn test_comparison_symmetry_consistency(array: &ArrayRef) {
833    let len = array.len();
834    if len == 0 {
835        return;
836    }
837
838    // Skip non-comparable types.
839    match array.dtype() {
840        DType::Null
841        | DType::List(..)
842        | DType::Struct(..)
843        | DType::Union(..)
844        | DType::Extension(_) => return,
845        _ => {}
846    }
847
848    // Get test values
849    let test_scalar = if len == 2 {
850        return;
851    } else {
852        array
853            .execute_scalar(len / 2, &mut LEGACY_SESSION.create_execution_ctx())
854            .vortex_expect("scalar_at should succeed in conformance test")
855    };
856
857    // Create a constant array with the test scalar for reverse comparison
858    let const_array = crate::arrays::ConstantArray::new(test_scalar, len);
859
860    // Test Gt vs Lt symmetry
861    if let (Ok(arr_gt_scalar), Ok(scalar_lt_arr)) = (
862        array
863            .clone()
864            .binary(const_array.clone().into_array(), Operator::Gt),
865        const_array
866            .clone()
867            .into_array()
868            .binary(array.clone(), Operator::Lt),
869    ) {
870        assert_eq!(
871            arr_gt_scalar.len(),
872            scalar_lt_arr.len(),
873            "Symmetric comparisons should have same length"
874        );
875
876        for i in 0..arr_gt_scalar.len() {
877            let arr_gt = arr_gt_scalar
878                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
879                .vortex_expect("scalar_at should succeed in conformance test");
880            let scalar_lt = scalar_lt_arr
881                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
882                .vortex_expect("scalar_at should succeed in conformance test");
883            assert_eq!(
884                arr_gt, scalar_lt,
885                "At index {i}: (array > scalar) should equal (scalar < array). \
886                 array > scalar = {arr_gt:?}, scalar < array = {scalar_lt:?}"
887            );
888        }
889    }
890
891    // Test Eq symmetry
892    if let (Ok(arr_eq_scalar), Ok(scalar_eq_arr)) = (
893        array
894            .clone()
895            .binary(const_array.clone().into_array(), Operator::Eq),
896        const_array.into_array().binary(array.clone(), Operator::Eq),
897    ) {
898        for i in 0..arr_eq_scalar.len() {
899            let arr_eq = arr_eq_scalar
900                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
901                .vortex_expect("scalar_at should succeed in conformance test");
902            let scalar_eq = scalar_eq_arr
903                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
904                .vortex_expect("scalar_at should succeed in conformance test");
905            assert_eq!(
906                arr_eq, scalar_eq,
907                "At index {i}: (array == scalar) should equal (scalar == array). \
908                 array == scalar = {arr_eq:?}, scalar == array = {scalar_eq:?}"
909            );
910        }
911    }
912}
913
914/// Tests that boolean operations follow De Morgan's laws.
915///
916/// # Invariants
917/// - `NOT(A AND B)` equals `(NOT A) OR (NOT B)`
918/// - `NOT(A OR B)` equals `(NOT A) AND (NOT B)`
919///
920/// # Test Details
921/// - If the array is boolean, uses it directly for testing boolean operations
922/// - Creates two boolean masks from patterns based on the array
923/// - Computes AND/OR operations and their inversions
924/// - Verifies De Morgan's laws hold for all elements
925///
926/// # Why This Matters
927/// Boolean operations must maintain logical consistency across encodings.
928/// This test catches bugs where encodings might optimize boolean operations
929/// incorrectly, breaking fundamental logical properties.
930fn test_boolean_demorgan_consistency(array: &ArrayRef) {
931    if !matches!(array.dtype(), DType::Bool(_)) {
932        return;
933    }
934
935    let bool_mask = {
936        let mask_pattern: Vec<bool> = (0..array.len()).map(|i| i % 3 == 0).collect();
937        BoolArray::from_iter(mask_pattern)
938    };
939    let bool_mask = bool_mask.into_array();
940
941    // Test first De Morgan's law: NOT(A AND B) = (NOT A) OR (NOT B)
942    if let (Ok(a_and_b), Ok(not_a), Ok(not_b)) = (
943        array.clone().binary(bool_mask.clone(), Operator::And),
944        array.not(),
945        bool_mask.not(),
946    ) {
947        let not_a_and_b = a_and_b
948            .not()
949            .vortex_expect("not should succeed in conformance test");
950        let not_a_or_not_b = not_a
951            .binary(not_b, Operator::Or)
952            .vortex_expect("or should succeed in conformance test");
953
954        assert_eq!(
955            not_a_and_b.len(),
956            not_a_or_not_b.len(),
957            "De Morgan's law results should have same length"
958        );
959
960        for i in 0..not_a_and_b.len() {
961            let left = not_a_and_b
962                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
963                .vortex_expect("scalar_at should succeed in conformance test");
964            let right = not_a_or_not_b
965                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
966                .vortex_expect("scalar_at should succeed in conformance test");
967            assert_eq!(
968                left, right,
969                "De Morgan's first law failed at index {i}: \
970                 NOT(A AND B) = {left:?}, (NOT A) OR (NOT B) = {right:?}"
971            );
972        }
973    }
974
975    // Test second De Morgan's law: NOT(A OR B) = (NOT A) AND (NOT B)
976    if let (Ok(a_or_b), Ok(not_a), Ok(not_b)) = (
977        array.clone().binary(bool_mask.clone(), Operator::Or),
978        array.not(),
979        bool_mask.not(),
980    ) {
981        let not_a_or_b = a_or_b
982            .not()
983            .vortex_expect("not should succeed in conformance test");
984        let not_a_and_not_b = not_a
985            .binary(not_b, Operator::And)
986            .vortex_expect("and should succeed in conformance test");
987
988        for i in 0..not_a_or_b.len() {
989            let left = not_a_or_b
990                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
991                .vortex_expect("scalar_at should succeed in conformance test");
992            let right = not_a_and_not_b
993                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
994                .vortex_expect("scalar_at should succeed in conformance test");
995            assert_eq!(
996                left, right,
997                "De Morgan's second law failed at index {i}: \
998                 NOT(A OR B) = {left:?}, (NOT A) AND (NOT B) = {right:?}"
999            );
1000        }
1001    }
1002}
1003
1004/// Tests that slice and aggregate operations produce consistent results.
1005///
1006/// # Invariants
1007/// - Aggregating a sliced array should equal aggregating the corresponding
1008///   elements from the canonical form
1009/// - This applies to sum, count, min/max, and other aggregate functions
1010///
1011/// # Test Details
1012/// - Slices the array and computes aggregates
1013/// - Compares against aggregating the canonical form's slice
1014/// - Tests multiple aggregate functions where applicable
1015///
1016/// # Why This Matters
1017/// Aggregate operations on sliced arrays must produce correct results
1018/// regardless of the underlying encoding's offset handling.
1019fn test_slice_aggregate_consistency(array: &ArrayRef) {
1020    use crate::aggregate_fn::fns::min_max::min_max;
1021    use crate::aggregate_fn::fns::nan_count::nan_count;
1022    use crate::aggregate_fn::fns::sum::sum;
1023    use crate::dtype::DType;
1024
1025    let mut ctx = LEGACY_SESSION.create_execution_ctx();
1026
1027    let len = array.len();
1028    if len < 5 {
1029        return; // Need enough elements for meaningful slice
1030    }
1031
1032    // Define slice bounds
1033    let start = 1;
1034    let end = (len - 1).min(start + 10); // Take up to 10 elements
1035
1036    // Get sliced array and canonical slice
1037    let sliced = array
1038        .slice(start..end)
1039        .vortex_expect("slice should succeed in conformance test");
1040    #[expect(deprecated)]
1041    let canonical = array.to_canonical().vortex_expect("to_canonical failed");
1042    let canonical_sliced = canonical
1043        .into_array()
1044        .slice(start..end)
1045        .vortex_expect("slice should succeed in conformance test");
1046
1047    // Test null count through invalid_count
1048    let sliced_invalid_count = sliced
1049        .invalid_count(&mut ctx)
1050        .vortex_expect("invalid_count should succeed in conformance test");
1051    let canonical_invalid_count = canonical_sliced
1052        .invalid_count(&mut ctx)
1053        .vortex_expect("invalid_count should succeed in conformance test");
1054    assert_eq!(
1055        sliced_invalid_count, canonical_invalid_count,
1056        "null_count on sliced array should match canonical. \
1057             Sliced: {sliced_invalid_count}, Canonical: {canonical_invalid_count}",
1058    );
1059
1060    // Test sum for numeric types
1061    if !matches!(array.dtype(), DType::Primitive(..)) {
1062        return;
1063    }
1064
1065    if let (Ok(slice_sum), Ok(canonical_sum)) =
1066        (sum(&sliced, &mut ctx), sum(&canonical_sliced, &mut ctx))
1067    {
1068        // Compare sum scalars
1069        assert_eq!(
1070            slice_sum, canonical_sum,
1071            "sum on sliced array should match canonical. \
1072                 Sliced: {slice_sum:?}, Canonical: {canonical_sum:?}"
1073        );
1074    }
1075
1076    // Test min_max
1077    if let (Ok(slice_minmax), Ok(canonical_minmax)) = (
1078        min_max(&sliced, &mut ctx),
1079        min_max(&canonical_sliced, &mut ctx),
1080    ) {
1081        match (slice_minmax, canonical_minmax) {
1082            (Some(s_result), Some(c_result)) => {
1083                assert_eq!(
1084                    s_result.min, c_result.min,
1085                    "min on sliced array should match canonical. \
1086                         Sliced: {:?}, Canonical: {:?}",
1087                    s_result.min, c_result.min
1088                );
1089                assert_eq!(
1090                    s_result.max, c_result.max,
1091                    "max on sliced array should match canonical. \
1092                         Sliced: {:?}, Canonical: {:?}",
1093                    s_result.max, c_result.max
1094                );
1095            }
1096            (None, None) => {} // Both empty, OK
1097            _ => vortex_panic!("min_max results don't match"),
1098        }
1099    }
1100
1101    // Test nan_count for floating point types
1102    if array.dtype().is_float()
1103        && let (Ok(slice_nan_count), Ok(canonical_nan_count)) = (
1104            nan_count(&sliced, &mut ctx),
1105            nan_count(&canonical_sliced, &mut ctx),
1106        )
1107    {
1108        assert_eq!(
1109            slice_nan_count, canonical_nan_count,
1110            "nan_count on sliced array should match canonical. \
1111                 Sliced: {slice_nan_count}, Canonical: {canonical_nan_count}"
1112        );
1113    }
1114}
1115
1116/// Tests that cast operations preserve array properties when sliced.
1117///
1118/// # Invariant
1119/// `cast(slice(array, start, end), dtype)` should equal `slice(cast(array, dtype), start, end)`
1120///
1121/// # Test Details
1122/// - Slices the array from index 2 to 7 (or len-2 if smaller)
1123/// - Casts the sliced array to a different type
1124/// - Compares against the canonical form of the array (without slicing or casting the canonical form)
1125/// - Verifies both approaches produce identical results
1126///
1127/// # Why This Matters
1128/// This test specifically catches bugs where encodings (like RunEndArray) fail to preserve
1129/// offset information during cast operations. Such bugs can lead to incorrect data being
1130/// returned after casting a sliced array.
1131fn test_cast_slice_consistency(array: &ArrayRef) {
1132    let len = array.len();
1133    if len < 5 {
1134        return; // Need at least 5 elements for meaningful slice
1135    }
1136
1137    // Define slice bounds
1138    let start = 2;
1139    let end = 7.min(len - 2).max(start + 1); // Ensure we have at least 1 element
1140
1141    // Get canonical form of the original array
1142    #[expect(deprecated)]
1143    let canonical = array.to_canonical().vortex_expect("to_canonical failed");
1144
1145    // Choose appropriate target dtype based on the array's type
1146    let target_dtypes = match array.dtype() {
1147        DType::Null => vec![],
1148        DType::Bool(nullability) => vec![
1149            DType::Primitive(PType::U8, *nullability),
1150            DType::Primitive(PType::I32, *nullability),
1151        ],
1152        DType::Primitive(ptype, nullability) => {
1153            let mut targets = vec![];
1154            // Test nullability changes
1155            let opposite_nullability = match nullability {
1156                Nullability::NonNullable => Nullability::Nullable,
1157                Nullability::Nullable => Nullability::NonNullable,
1158            };
1159            targets.push(DType::Primitive(*ptype, opposite_nullability));
1160
1161            // Test widening casts
1162            match ptype {
1163                PType::U8 => {
1164                    targets.push(DType::Primitive(PType::U16, *nullability));
1165                    targets.push(DType::Primitive(PType::I16, *nullability));
1166                }
1167                PType::U16 => {
1168                    targets.push(DType::Primitive(PType::U32, *nullability));
1169                    targets.push(DType::Primitive(PType::I32, *nullability));
1170                }
1171                PType::U32 => {
1172                    targets.push(DType::Primitive(PType::U64, *nullability));
1173                    targets.push(DType::Primitive(PType::I64, *nullability));
1174                }
1175                PType::U64 => {
1176                    targets.push(DType::Primitive(PType::F64, *nullability));
1177                }
1178                PType::I8 => {
1179                    targets.push(DType::Primitive(PType::I16, *nullability));
1180                    targets.push(DType::Primitive(PType::F32, *nullability));
1181                }
1182                PType::I16 => {
1183                    targets.push(DType::Primitive(PType::I32, *nullability));
1184                    targets.push(DType::Primitive(PType::F32, *nullability));
1185                }
1186                PType::I32 => {
1187                    targets.push(DType::Primitive(PType::I64, *nullability));
1188                    targets.push(DType::Primitive(PType::F64, *nullability));
1189                }
1190                PType::I64 => {
1191                    targets.push(DType::Primitive(PType::F64, *nullability));
1192                }
1193                PType::F16 => {
1194                    targets.push(DType::Primitive(PType::F32, *nullability));
1195                }
1196                PType::F32 => {
1197                    targets.push(DType::Primitive(PType::F64, *nullability));
1198                    targets.push(DType::Primitive(PType::I32, *nullability));
1199                }
1200                PType::F64 => {
1201                    targets.push(DType::Primitive(PType::I64, *nullability));
1202                }
1203            }
1204            targets
1205        }
1206        DType::Decimal(decimal_type, nullability) => {
1207            let opposite = match nullability {
1208                Nullability::NonNullable => Nullability::Nullable,
1209                Nullability::Nullable => Nullability::NonNullable,
1210            };
1211            vec![DType::Decimal(*decimal_type, opposite)]
1212        }
1213        DType::Utf8(nullability) => {
1214            let opposite = match nullability {
1215                Nullability::NonNullable => Nullability::Nullable,
1216                Nullability::Nullable => Nullability::NonNullable,
1217            };
1218            vec![DType::Utf8(opposite), DType::Binary(*nullability)]
1219        }
1220        DType::Binary(nullability) => {
1221            let opposite = match nullability {
1222                Nullability::NonNullable => Nullability::Nullable,
1223                Nullability::Nullable => Nullability::NonNullable,
1224            };
1225            vec![
1226                DType::Binary(opposite),
1227                DType::Utf8(*nullability), // May fail if not valid UTF-8
1228            ]
1229        }
1230        DType::List(element_type, nullability) => {
1231            let opposite = match nullability {
1232                Nullability::NonNullable => Nullability::Nullable,
1233                Nullability::Nullable => Nullability::NonNullable,
1234            };
1235            vec![DType::List(Arc::clone(element_type), opposite)]
1236        }
1237        DType::FixedSizeList(element_type, list_size, nullability) => {
1238            let opposite = match nullability {
1239                Nullability::NonNullable => Nullability::Nullable,
1240                Nullability::Nullable => Nullability::NonNullable,
1241            };
1242            vec![DType::FixedSizeList(
1243                Arc::clone(element_type),
1244                *list_size,
1245                opposite,
1246            )]
1247        }
1248        DType::Struct(fields, nullability) => {
1249            let opposite = match nullability {
1250                Nullability::NonNullable => Nullability::Nullable,
1251                Nullability::Nullable => Nullability::NonNullable,
1252            };
1253            vec![DType::Struct(fields.clone(), opposite)]
1254        }
1255        DType::Union(..) => todo!("TODO(connor)[Union]: unimplemented"),
1256        DType::Variant(_) => unimplemented!(),
1257        DType::Extension(_) => vec![], // Extension types typically only cast to themselves
1258    };
1259
1260    // Test each target dtype
1261    for target_dtype in target_dtypes {
1262        // Slice the array
1263        let sliced = array
1264            .slice(start..end)
1265            .vortex_expect("slice should succeed in conformance test");
1266
1267        // Try to cast the sliced array (force execution via to_canonical)
1268        let slice_then_cast = match sliced.cast(target_dtype.clone()).and_then(|a| {
1269            #[expect(deprecated)]
1270            a.to_canonical().map(|c| c.into_array())
1271        }) {
1272            Ok(result) => result,
1273            Err(_) => continue, // Skip if cast fails
1274        };
1275
1276        // Verify against canonical form
1277        assert_eq!(
1278            slice_then_cast.len(),
1279            end - start,
1280            "Sliced and casted array should have length {}, but has {}",
1281            end - start,
1282            slice_then_cast.len()
1283        );
1284
1285        // Compare each value against the canonical form
1286        for i in 0..slice_then_cast.len() {
1287            let slice_cast_val = slice_then_cast
1288                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
1289                .vortex_expect("scalar_at should succeed in conformance test");
1290
1291            // Get the corresponding value from the canonical array (adjusted for slice offset)
1292            let canonical_val = canonical
1293                .clone()
1294                .into_array()
1295                .execute_scalar(start + i, &mut LEGACY_SESSION.create_execution_ctx())
1296                .vortex_expect("scalar_at should succeed in conformance test");
1297
1298            // Cast the canonical scalar to the target dtype
1299            let expected_val = match canonical_val.cast(&target_dtype) {
1300                Ok(val) => val,
1301                Err(_) => {
1302                    // If scalar cast fails, we can't compare - skip this target dtype
1303                    // This can happen for some type conversions that aren't supported at scalar level
1304                    break;
1305                }
1306            };
1307
1308            assert_eq!(
1309                slice_cast_val,
1310                expected_val,
1311                "Cast of sliced array produced incorrect value at index {i}. \
1312                 Got: {slice_cast_val:?}, Expected: {expected_val:?} \
1313                 (canonical value at index {}: {canonical_val:?})\n\
1314                 This likely indicates the array encoding doesn't preserve offset information during cast.",
1315                start + i
1316            );
1317        }
1318
1319        // Also test the other way: cast then slice
1320        let casted = match array.clone().cast(target_dtype.clone()).and_then(|a| {
1321            #[expect(deprecated)]
1322            a.to_canonical().map(|c| c.into_array())
1323        }) {
1324            Ok(result) => result,
1325            Err(_) => continue, // Skip if cast fails
1326        };
1327        let cast_then_slice = casted
1328            .slice(start..end)
1329            .vortex_expect("slice should succeed in conformance test");
1330
1331        // Verify the two approaches produce identical results
1332        assert_eq!(
1333            slice_then_cast.len(),
1334            cast_then_slice.len(),
1335            "Slice-then-cast and cast-then-slice should produce arrays of the same length"
1336        );
1337
1338        for i in 0..slice_then_cast.len() {
1339            let slice_cast_val = slice_then_cast
1340                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
1341                .vortex_expect("scalar_at should succeed in conformance test");
1342            let cast_slice_val = cast_then_slice
1343                .execute_scalar(i, &mut LEGACY_SESSION.create_execution_ctx())
1344                .vortex_expect("scalar_at should succeed in conformance test");
1345            assert_eq!(
1346                slice_cast_val, cast_slice_val,
1347                "Slice-then-cast and cast-then-slice produced different values at index {i}. \
1348                 Slice-then-cast: {slice_cast_val:?}, Cast-then-slice: {cast_slice_val:?}"
1349            );
1350        }
1351    }
1352}
1353
1354/// Run all consistency tests on an array.
1355///
1356/// This function executes a comprehensive suite of consistency tests that verify
1357/// the correctness of compute operations on Vortex arrays.
1358///
1359/// # Test Suite Overview
1360///
1361/// ## Core Operation Consistency
1362/// - **Filter/Take**: Verifies `filter(array, mask)` equals `take(array, true_indices)`
1363/// - **Mask Composition**: Ensures sequential masks equal combined masks
1364/// - **Slice/Filter**: Checks contiguous filters equal slice operations
1365/// - **Take/Slice**: Validates sequential takes equal slice operations
1366/// - **Cast/Slice**: Ensures cast operations preserve sliced array properties
1367///
1368/// ## Boolean Operations
1369/// - **De Morgan's Laws**: Verifies boolean operations follow logical laws
1370///
1371/// ## Comparison Operations
1372/// - **Inverse Relationships**: Verifies logical inverses (Eq/NotEq, Gt/Lte, Lt/Gte)
1373/// - **Symmetry**: Ensures proper ordering relationships when operands are swapped
1374///
1375/// ## Aggregate Operations
1376/// - **Slice/Aggregate**: Verifies aggregates on sliced arrays match canonical
1377///
1378/// ## Identity Operations
1379/// - **Filter Identity**: All-true mask preserves the array
1380/// - **Mask Identity**: All-false mask preserves values (as nullable)
1381/// - **Take Identity**: Taking all indices preserves the array
1382///
1383/// ## Edge Cases
1384/// - **Empty Operations**: Empty filters, takes, and slices behave correctly
1385/// - **Single Element**: Operations work with single-element arrays
1386/// - **Repeated Indices**: Take with duplicate indices works correctly
1387///
1388/// ## Null Handling
1389/// - **Nullable Indices**: Null indices produce null values
1390/// - **Mask/Filter Interaction**: Masking then filtering behaves predictably
1391///
1392/// ## Large Arrays
1393/// - **Performance**: Operations scale correctly to large arrays (1000+ elements)
1394/// ```text
1395pub fn test_array_consistency(array: &ArrayRef) {
1396    // Core operation consistency
1397    test_filter_take_consistency(array);
1398    test_double_mask_consistency(array);
1399    test_slice_filter_consistency(array);
1400    test_take_slice_consistency(array);
1401    test_cast_slice_consistency(array);
1402
1403    // Boolean operations
1404    test_boolean_demorgan_consistency(array);
1405
1406    // Comparison operations
1407    test_comparison_inverse_consistency(array);
1408    test_comparison_symmetry_consistency(array);
1409
1410    // Aggregate operations
1411    test_slice_aggregate_consistency(array);
1412
1413    // Identity operations
1414    test_filter_identity(array);
1415    test_mask_identity(array);
1416    test_take_preserves_properties(array);
1417
1418    // Ordering and correctness
1419    test_filter_preserves_order(array);
1420    test_take_repeated_indices(array);
1421
1422    // Null handling
1423    test_mask_filter_null_consistency(array);
1424    test_nullable_indices_consistency(array);
1425
1426    // Edge cases
1427    test_empty_operations_consistency(array);
1428    test_large_array_consistency(array);
1429}