Skip to main content

xlog_cuda_tests/categories/
c14_integer.rs

1//! Category 14: Integer edge cases
2//!
3//! Tests integer boundary conditions including overflow boundaries,
4//! full range coverage, signed comparison, and wraparound keys.
5
6use crate::harness::{CategoryResult, TestContext, TestResult};
7use std::collections::HashSet;
8use std::time::Instant;
9use xlog_core::{ScalarType, Schema};
10
11/// Run all tests in this category.
12pub fn run_all(ctx: &TestContext) -> CategoryResult {
13    let mut results = CategoryResult::new("c14_integer");
14    let start = Instant::now();
15
16    results.add_result(test_i64_overflow_boundaries(ctx));
17    results.add_result(test_u64_overflow_boundaries(ctx));
18    results.add_result(test_u32_full_range(ctx));
19    results.add_result(test_i64_signed_comparison(ctx));
20    results.add_result(test_integer_wraparound_keys(ctx));
21
22    results.set_duration(start.elapsed());
23    results
24}
25
26/// Test 1: Test i64::MIN, i64::MAX, 0, -1, 1.
27///
28/// Verifies that signed 64-bit integer boundary values are handled correctly
29/// in sorting and filtering operations.
30fn test_i64_overflow_boundaries(ctx: &TestContext) -> TestResult {
31    let start = Instant::now();
32    let schema = Schema::new(vec![("val".to_string(), ScalarType::I64)]);
33
34    // Create data with i64 boundary values
35    let data: Vec<i64> = vec![
36        i64::MIN,
37        i64::MAX,
38        0,
39        -1,
40        1,
41        i64::MIN + 1,
42        i64::MAX - 1,
43        -2,
44        2,
45        i64::MIN / 2,
46        i64::MAX / 2,
47        -i64::MAX, // i64::MIN + 1
48        100,
49        -100,
50        1000000,
51        -1000000,
52    ];
53
54    let buffer = match ctx
55        .provider
56        .create_buffer_from_slice::<i64>(&data, schema.clone())
57    {
58        Ok(buf) => buf,
59        Err(e) => {
60            return TestResult::error(
61                "test_i64_overflow_boundaries",
62                start.elapsed(),
63                format!("Failed to create buffer: {}", e),
64            )
65        }
66    };
67
68    // Sort the buffer
69    let sorted = match ctx.provider.sort(&buffer, &[0]) {
70        Ok(s) => s,
71        Err(e) => {
72            return TestResult::error(
73                "test_i64_overflow_boundaries",
74                start.elapsed(),
75                format!("Sort failed: {}", e),
76            )
77        }
78    };
79
80    // Download sorted data
81    let sorted_data = match ctx.provider.download_column::<i64>(&sorted, 0) {
82        Ok(d) => d,
83        Err(e) => {
84            return TestResult::error(
85                "test_i64_overflow_boundaries",
86                start.elapsed(),
87                format!("Failed to download sorted column: {}", e),
88            )
89        }
90    };
91
92    // Verify row count preserved
93    if sorted_data.len() != data.len() {
94        return TestResult::error(
95            "test_i64_overflow_boundaries",
96            start.elapsed(),
97            format!(
98                "Sort returned {} rows, expected {}",
99                sorted_data.len(),
100                data.len()
101            ),
102        );
103    }
104
105    // Verify sort order
106    for i in 1..sorted_data.len() {
107        if sorted_data[i] < sorted_data[i - 1] {
108            return TestResult::error(
109                "test_i64_overflow_boundaries",
110                start.elapsed(),
111                format!(
112                    "Sort order incorrect at index {}: {} should be >= {}",
113                    i,
114                    sorted_data[i],
115                    sorted_data[i - 1]
116                ),
117            );
118        }
119    }
120
121    // Verify i64::MIN is first
122    if sorted_data[0] != i64::MIN {
123        return TestResult::error(
124            "test_i64_overflow_boundaries",
125            start.elapsed(),
126            format!(
127                "First element should be i64::MIN ({}), got {}",
128                i64::MIN,
129                sorted_data[0]
130            ),
131        );
132    }
133
134    // Verify i64::MAX is last
135    if sorted_data[sorted_data.len() - 1] != i64::MAX {
136        return TestResult::error(
137            "test_i64_overflow_boundaries",
138            start.elapsed(),
139            format!(
140                "Last element should be i64::MAX ({}), got {}",
141                i64::MAX,
142                sorted_data[sorted_data.len() - 1]
143            ),
144        );
145    }
146
147    // Verify all original values are present
148    let original_set: HashSet<i64> = data.iter().copied().collect();
149    let sorted_set: HashSet<i64> = sorted_data.iter().copied().collect();
150    if original_set != sorted_set {
151        return TestResult::error(
152            "test_i64_overflow_boundaries",
153            start.elapsed(),
154            "Some values were lost or changed during sort".to_string(),
155        );
156    }
157
158    // Test filter: keep only negative values
159    let mask: Vec<u8> = data.iter().map(|&v| if v < 0 { 1 } else { 0 }).collect();
160    let filtered = match ctx.provider.filter_by_mask(&buffer, &mask) {
161        Ok(f) => f,
162        Err(e) => {
163            return TestResult::error(
164                "test_i64_overflow_boundaries",
165                start.elapsed(),
166                format!("Filter failed: {}", e),
167            )
168        }
169    };
170
171    let filtered_data = match ctx.provider.download_column::<i64>(&filtered, 0) {
172        Ok(d) => d,
173        Err(e) => {
174            return TestResult::error(
175                "test_i64_overflow_boundaries",
176                start.elapsed(),
177                format!("Failed to download filtered column: {}", e),
178            )
179        }
180    };
181
182    // Verify filter result count
183    let expected_negative_count = data.iter().filter(|&&v| v < 0).count();
184    if filtered_data.len() != expected_negative_count {
185        return TestResult::error(
186            "test_i64_overflow_boundaries",
187            start.elapsed(),
188            format!(
189                "Filter returned {} rows, expected {}",
190                filtered_data.len(),
191                expected_negative_count
192            ),
193        );
194    }
195
196    // Verify i64::MIN is in the filtered results
197    if !filtered_data.contains(&i64::MIN) {
198        return TestResult::error(
199            "test_i64_overflow_boundaries",
200            start.elapsed(),
201            "Filtered negative values should include i64::MIN".to_string(),
202        );
203    }
204
205    if let Err(e) = ctx.sync_and_check() {
206        return TestResult::error(
207            "test_i64_overflow_boundaries",
208            start.elapsed(),
209            format!("Sync failed: {}", e),
210        );
211    }
212
213    TestResult::passed("test_i64_overflow_boundaries", start.elapsed())
214}
215
216/// Test 2: Test u64::MIN (0), u64::MAX.
217///
218/// Verifies that unsigned 64-bit integer boundary values are handled correctly.
219fn test_u64_overflow_boundaries(ctx: &TestContext) -> TestResult {
220    let start = Instant::now();
221    let schema = Schema::new(vec![("val".to_string(), ScalarType::U64)]);
222
223    // Create data with u64 boundary values
224    let data: Vec<u64> = vec![
225        u64::MIN, // 0
226        u64::MAX,
227        1,
228        u64::MAX - 1,
229        u64::MAX / 2,
230        u64::MAX / 4,
231        u64::MAX / 4 * 3,
232        100,
233        1000,
234        1_000_000,
235        1_000_000_000,
236        1_000_000_000_000,
237        // High bit patterns
238        0x8000_0000_0000_0000, // Only high bit set
239        0xFFFF_FFFF_0000_0000, // Upper 32 bits
240        0x0000_0000_FFFF_FFFF, // Lower 32 bits
241        0xAAAA_AAAA_AAAA_AAAA, // Alternating bits
242    ];
243
244    let buffer = match ctx
245        .provider
246        .create_buffer_from_slice::<u64>(&data, schema.clone())
247    {
248        Ok(buf) => buf,
249        Err(e) => {
250            return TestResult::error(
251                "test_u64_overflow_boundaries",
252                start.elapsed(),
253                format!("Failed to create buffer: {}", e),
254            )
255        }
256    };
257
258    // Sort the buffer
259    let sorted = match ctx.provider.sort(&buffer, &[0]) {
260        Ok(s) => s,
261        Err(e) => {
262            return TestResult::error(
263                "test_u64_overflow_boundaries",
264                start.elapsed(),
265                format!("Sort failed: {}", e),
266            )
267        }
268    };
269
270    // Download sorted data
271    let sorted_data = match ctx.provider.download_column::<u64>(&sorted, 0) {
272        Ok(d) => d,
273        Err(e) => {
274            return TestResult::error(
275                "test_u64_overflow_boundaries",
276                start.elapsed(),
277                format!("Failed to download sorted column: {}", e),
278            )
279        }
280    };
281
282    // Verify row count preserved
283    if sorted_data.len() != data.len() {
284        return TestResult::error(
285            "test_u64_overflow_boundaries",
286            start.elapsed(),
287            format!(
288                "Sort returned {} rows, expected {}",
289                sorted_data.len(),
290                data.len()
291            ),
292        );
293    }
294
295    // Verify sort order
296    for i in 1..sorted_data.len() {
297        if sorted_data[i] < sorted_data[i - 1] {
298            return TestResult::error(
299                "test_u64_overflow_boundaries",
300                start.elapsed(),
301                format!(
302                    "Sort order incorrect at index {}: {} should be >= {}",
303                    i,
304                    sorted_data[i],
305                    sorted_data[i - 1]
306                ),
307            );
308        }
309    }
310
311    // Verify u64::MIN (0) is first
312    if sorted_data[0] != u64::MIN {
313        return TestResult::error(
314            "test_u64_overflow_boundaries",
315            start.elapsed(),
316            format!("First element should be 0, got {}", sorted_data[0]),
317        );
318    }
319
320    // Verify u64::MAX is last
321    if sorted_data[sorted_data.len() - 1] != u64::MAX {
322        return TestResult::error(
323            "test_u64_overflow_boundaries",
324            start.elapsed(),
325            format!(
326                "Last element should be u64::MAX ({}), got {}",
327                u64::MAX,
328                sorted_data[sorted_data.len() - 1]
329            ),
330        );
331    }
332
333    // Verify all original values are present
334    let original_set: HashSet<u64> = data.iter().copied().collect();
335    let sorted_set: HashSet<u64> = sorted_data.iter().copied().collect();
336    if original_set != sorted_set {
337        return TestResult::error(
338            "test_u64_overflow_boundaries",
339            start.elapsed(),
340            "Some values were lost or changed during sort".to_string(),
341        );
342    }
343
344    // Verify high bit value is correctly ordered (not treated as negative)
345    let high_bit_value = 0x8000_0000_0000_0000u64;
346    let high_bit_pos = sorted_data.iter().position(|&v| v == high_bit_value);
347    if let Some(pos) = high_bit_pos {
348        // All values before should be less, all values after should be greater
349        for i in 0..pos {
350            if sorted_data[i] >= high_bit_value {
351                return TestResult::error(
352                    "test_u64_overflow_boundaries",
353                    start.elapsed(),
354                    format!(
355                        "Value {} at position {} should be less than {} at position {}",
356                        sorted_data[i], i, high_bit_value, pos
357                    ),
358                );
359            }
360        }
361    }
362
363    if let Err(e) = ctx.sync_and_check() {
364        return TestResult::error(
365            "test_u64_overflow_boundaries",
366            start.elapsed(),
367            format!("Sync failed: {}", e),
368        );
369    }
370
371    TestResult::passed("test_u64_overflow_boundaries", start.elapsed())
372}
373
374/// Test 3: Test across full u32 range.
375///
376/// Verifies that operations work correctly across the full range of u32 values,
377/// including boundary values and values with specific bit patterns.
378fn test_u32_full_range(ctx: &TestContext) -> TestResult {
379    let start = Instant::now();
380    let schema = Schema::new(vec![("val".to_string(), ScalarType::U32)]);
381
382    // Create data spanning the full u32 range
383    let mut data: Vec<u32> = vec![
384        u32::MIN, // 0
385        u32::MAX,
386        1,
387        u32::MAX - 1,
388        u32::MAX / 2,
389        u32::MAX / 4,
390        u32::MAX / 4 * 3,
391        // Byte boundary values
392        0xFF,
393        0xFF00,
394        0xFF_0000,
395        0xFF00_0000,
396        // Bit patterns
397        0x8000_0000, // Only high bit set
398        0xFFFF_0000, // Upper 16 bits
399        0x0000_FFFF, // Lower 16 bits
400        0xAAAA_AAAA, // Alternating bits
401        0x5555_5555, // Inverted alternating bits
402    ];
403
404    // Add evenly distributed values across the range
405    for i in 0..16 {
406        data.push((u32::MAX as u64 * i / 16) as u32);
407    }
408
409    let buffer = match ctx
410        .provider
411        .create_buffer_from_slice::<u32>(&data, schema.clone())
412    {
413        Ok(buf) => buf,
414        Err(e) => {
415            return TestResult::error(
416                "test_u32_full_range",
417                start.elapsed(),
418                format!("Failed to create buffer: {}", e),
419            )
420        }
421    };
422
423    // Sort the buffer
424    let sorted = match ctx.provider.sort(&buffer, &[0]) {
425        Ok(s) => s,
426        Err(e) => {
427            return TestResult::error(
428                "test_u32_full_range",
429                start.elapsed(),
430                format!("Sort failed: {}", e),
431            )
432        }
433    };
434
435    // Download sorted data
436    let sorted_data = match ctx.provider.download_column::<u32>(&sorted, 0) {
437        Ok(d) => d,
438        Err(e) => {
439            return TestResult::error(
440                "test_u32_full_range",
441                start.elapsed(),
442                format!("Failed to download sorted column: {}", e),
443            )
444        }
445    };
446
447    // Verify row count preserved
448    if sorted_data.len() != data.len() {
449        return TestResult::error(
450            "test_u32_full_range",
451            start.elapsed(),
452            format!(
453                "Sort returned {} rows, expected {}",
454                sorted_data.len(),
455                data.len()
456            ),
457        );
458    }
459
460    // Verify sort order
461    for i in 1..sorted_data.len() {
462        if sorted_data[i] < sorted_data[i - 1] {
463            return TestResult::error(
464                "test_u32_full_range",
465                start.elapsed(),
466                format!(
467                    "Sort order incorrect at index {}: {} should be >= {}",
468                    i,
469                    sorted_data[i],
470                    sorted_data[i - 1]
471                ),
472            );
473        }
474    }
475
476    // Verify 0 is first
477    if sorted_data[0] != 0 {
478        return TestResult::error(
479            "test_u32_full_range",
480            start.elapsed(),
481            format!("First element should be 0, got {}", sorted_data[0]),
482        );
483    }
484
485    // Verify u32::MAX is last
486    if sorted_data[sorted_data.len() - 1] != u32::MAX {
487        return TestResult::error(
488            "test_u32_full_range",
489            start.elapsed(),
490            format!(
491                "Last element should be u32::MAX ({}), got {}",
492                u32::MAX,
493                sorted_data[sorted_data.len() - 1]
494            ),
495        );
496    }
497
498    // Test filter: keep only values in upper half of range
499    let threshold = u32::MAX / 2;
500    let mask: Vec<u8> = data
501        .iter()
502        .map(|&v| if v >= threshold { 1 } else { 0 })
503        .collect();
504    let filtered = match ctx.provider.filter_by_mask(&buffer, &mask) {
505        Ok(f) => f,
506        Err(e) => {
507            return TestResult::error(
508                "test_u32_full_range",
509                start.elapsed(),
510                format!("Filter failed: {}", e),
511            )
512        }
513    };
514
515    let filtered_data = match ctx.provider.download_column::<u32>(&filtered, 0) {
516        Ok(d) => d,
517        Err(e) => {
518            return TestResult::error(
519                "test_u32_full_range",
520                start.elapsed(),
521                format!("Failed to download filtered column: {}", e),
522            )
523        }
524    };
525
526    // Verify all filtered values are >= threshold
527    for &val in &filtered_data {
528        if val < threshold {
529            return TestResult::error(
530                "test_u32_full_range",
531                start.elapsed(),
532                format!(
533                    "Filtered value {} should be >= threshold {}",
534                    val, threshold
535                ),
536            );
537        }
538    }
539
540    if let Err(e) = ctx.sync_and_check() {
541        return TestResult::error(
542            "test_u32_full_range",
543            start.elapsed(),
544            format!("Sync failed: {}", e),
545        );
546    }
547
548    TestResult::passed("test_u32_full_range", start.elapsed())
549}
550
551/// Test 4: Verify signed comparison works correctly.
552///
553/// Tests that i64 values are compared as signed integers (negative < positive),
554/// not as unsigned bit patterns.
555fn test_i64_signed_comparison(ctx: &TestContext) -> TestResult {
556    let start = Instant::now();
557    let schema = Schema::new(vec![("val".to_string(), ScalarType::I64)]);
558
559    // Create data that would sort differently if treated as unsigned
560    // In unsigned interpretation, negative numbers have high bit set and would sort last
561    let data: Vec<i64> = vec![
562        -1, // 0xFFFF_FFFF_FFFF_FFFF as unsigned
563        -2, // 0xFFFF_FFFF_FFFF_FFFE as unsigned
564        -1000,
565        -1_000_000,
566        0,
567        1,
568        2,
569        1000,
570        1_000_000,
571        i64::MIN, // 0x8000_0000_0000_0000 - would sort middle if unsigned
572        i64::MAX, // 0x7FFF_FFFF_FFFF_FFFF - would sort before -1 if unsigned
573    ];
574
575    let buffer = match ctx
576        .provider
577        .create_buffer_from_slice::<i64>(&data, schema.clone())
578    {
579        Ok(buf) => buf,
580        Err(e) => {
581            return TestResult::error(
582                "test_i64_signed_comparison",
583                start.elapsed(),
584                format!("Failed to create buffer: {}", e),
585            )
586        }
587    };
588
589    // Sort the buffer
590    let sorted = match ctx.provider.sort(&buffer, &[0]) {
591        Ok(s) => s,
592        Err(e) => {
593            return TestResult::error(
594                "test_i64_signed_comparison",
595                start.elapsed(),
596                format!("Sort failed: {}", e),
597            )
598        }
599    };
600
601    // Download sorted data
602    let sorted_data = match ctx.provider.download_column::<i64>(&sorted, 0) {
603        Ok(d) => d,
604        Err(e) => {
605            return TestResult::error(
606                "test_i64_signed_comparison",
607                start.elapsed(),
608                format!("Failed to download sorted column: {}", e),
609            )
610        }
611    };
612
613    // Verify signed sort order
614    for i in 1..sorted_data.len() {
615        if sorted_data[i] < sorted_data[i - 1] {
616            return TestResult::error(
617                "test_i64_signed_comparison",
618                start.elapsed(),
619                format!(
620                    "Signed sort order incorrect at index {}: {} should be >= {}",
621                    i,
622                    sorted_data[i],
623                    sorted_data[i - 1]
624                ),
625            );
626        }
627    }
628
629    // Verify expected order: i64::MIN should come before negative values
630    // Expected order: i64::MIN, -1_000_000, -1000, -2, -1, 0, 1, 2, 1000, 1_000_000, i64::MAX
631    let mut expected = data.clone();
632    expected.sort();
633
634    if sorted_data != expected {
635        return TestResult::error(
636            "test_i64_signed_comparison",
637            start.elapsed(),
638            format!(
639                "Sort order doesn't match expected signed order.\nGot: {:?}\nExpected: {:?}",
640                sorted_data, expected
641            ),
642        );
643    }
644
645    // Specifically verify that negative values come before positive values
646    let first_non_negative_idx = sorted_data.iter().position(|&v| v >= 0);
647    if let Some(idx) = first_non_negative_idx {
648        // All values before idx should be negative
649        for i in 0..idx {
650            if sorted_data[i] >= 0 {
651                return TestResult::error(
652                    "test_i64_signed_comparison",
653                    start.elapsed(),
654                    format!(
655                        "Negative values should come before positive: found {} at index {}",
656                        sorted_data[i], i
657                    ),
658                );
659            }
660        }
661    }
662
663    // Verify i64::MIN (most negative) is first
664    if sorted_data[0] != i64::MIN {
665        return TestResult::error(
666            "test_i64_signed_comparison",
667            start.elapsed(),
668            format!(
669                "First element should be i64::MIN ({}), got {} - may be treated as unsigned",
670                i64::MIN,
671                sorted_data[0]
672            ),
673        );
674    }
675
676    // Verify i64::MAX (most positive) is last
677    if sorted_data[sorted_data.len() - 1] != i64::MAX {
678        return TestResult::error(
679            "test_i64_signed_comparison",
680            start.elapsed(),
681            format!(
682                "Last element should be i64::MAX ({}), got {}",
683                i64::MAX,
684                sorted_data[sorted_data.len() - 1]
685            ),
686        );
687    }
688
689    if let Err(e) = ctx.sync_and_check() {
690        return TestResult::error(
691            "test_i64_signed_comparison",
692            start.elapsed(),
693            format!("Sync failed: {}", e),
694        );
695    }
696
697    TestResult::passed("test_i64_signed_comparison", start.elapsed())
698}
699
700/// Test 5: Test keys near wraparound boundaries in hash tables.
701///
702/// Verifies that hash join operations work correctly with keys near integer
703/// boundaries that might cause hash collisions or wraparound issues.
704fn test_integer_wraparound_keys(ctx: &TestContext) -> TestResult {
705    let start = Instant::now();
706
707    let left_schema = Schema::new(vec![
708        ("key".to_string(), ScalarType::U32),
709        ("lval".to_string(), ScalarType::U32),
710    ]);
711    let right_schema = Schema::new(vec![
712        ("key".to_string(), ScalarType::U32),
713        ("rval".to_string(), ScalarType::U32),
714    ]);
715
716    // Keys near wraparound boundary (u32::MAX and near it)
717    let left_keys: Vec<u32> = vec![
718        u32::MAX,
719        u32::MAX - 1,
720        u32::MAX - 2,
721        0,
722        1,
723        2,
724        u32::MAX / 2,
725        u32::MAX / 2 + 1,
726        // Keys that might cause hash collisions
727        0x8000_0000,
728        0x8000_0001,
729        0xFFFF_FFFF,
730        0xFFFF_FFFE,
731    ];
732    let left_vals: Vec<u32> = left_keys.iter().map(|&k| k.wrapping_add(100)).collect();
733
734    // Right keys - subset of left keys
735    let right_keys: Vec<u32> = vec![u32::MAX, u32::MAX - 1, 0, 1, u32::MAX / 2, 0x8000_0000];
736    let right_vals: Vec<u32> = right_keys.iter().map(|&k| k.wrapping_mul(10)).collect();
737
738    let left_buffer = match ctx
739        .provider
740        .create_buffer_from_u32_columns(&[&left_keys, &left_vals], left_schema.clone())
741    {
742        Ok(buf) => buf,
743        Err(e) => {
744            return TestResult::error(
745                "test_integer_wraparound_keys",
746                start.elapsed(),
747                format!("Failed to create left buffer: {}", e),
748            )
749        }
750    };
751
752    let right_buffer = match ctx
753        .provider
754        .create_buffer_from_u32_columns(&[&right_keys, &right_vals], right_schema.clone())
755    {
756        Ok(buf) => buf,
757        Err(e) => {
758            return TestResult::error(
759                "test_integer_wraparound_keys",
760                start.elapsed(),
761                format!("Failed to create right buffer: {}", e),
762            )
763        }
764    };
765
766    // Perform hash join
767    let joined = match ctx
768        .provider
769        .hash_join(&left_buffer, &right_buffer, &[0], &[0])
770    {
771        Ok(j) => j,
772        Err(e) => {
773            return TestResult::error(
774                "test_integer_wraparound_keys",
775                start.elapsed(),
776                format!("Hash join failed: {}", e),
777            )
778        }
779    };
780
781    // Calculate expected join count
782    let right_key_set: HashSet<u32> = right_keys.iter().copied().collect();
783    let expected_matches = left_keys
784        .iter()
785        .filter(|k| right_key_set.contains(k))
786        .count();
787
788    if ctx.device_row_count(&joined) != expected_matches as u64 {
789        return TestResult::error(
790            "test_integer_wraparound_keys",
791            start.elapsed(),
792            format!(
793                "Join returned {} rows, expected {}",
794                ctx.device_row_count(&joined),
795                expected_matches
796            ),
797        );
798    }
799
800    // Download and verify join results
801    let joined_keys = match ctx.provider.download_column::<u32>(&joined, 0) {
802        Ok(d) => d,
803        Err(e) => {
804            return TestResult::error(
805                "test_integer_wraparound_keys",
806                start.elapsed(),
807                format!("Failed to download joined keys: {}", e),
808            )
809        }
810    };
811
812    let joined_lvals = match ctx.provider.download_column::<u32>(&joined, 1) {
813        Ok(d) => d,
814        Err(e) => {
815            return TestResult::error(
816                "test_integer_wraparound_keys",
817                start.elapsed(),
818                format!("Failed to download joined lvals: {}", e),
819            )
820        }
821    };
822
823    let joined_rvals = match ctx.provider.download_column::<u32>(&joined, 2) {
824        Ok(d) => d,
825        Err(e) => {
826            return TestResult::error(
827                "test_integer_wraparound_keys",
828                start.elapsed(),
829                format!("Failed to download joined rvals: {}", e),
830            )
831        }
832    };
833
834    // Verify each joined row
835    for i in 0..ctx.device_row_count(&joined) as usize {
836        let key = joined_keys[i];
837        let lval = joined_lvals[i];
838        let rval = joined_rvals[i];
839
840        // Verify lval matches expected pattern
841        let expected_lval = key.wrapping_add(100);
842        if lval != expected_lval {
843            return TestResult::error(
844                "test_integer_wraparound_keys",
845                start.elapsed(),
846                format!(
847                    "Row {}: lval {} doesn't match expected {} for key {}",
848                    i, lval, expected_lval, key
849                ),
850            );
851        }
852
853        // Verify rval matches expected pattern
854        let expected_rval = key.wrapping_mul(10);
855        if rval != expected_rval {
856            return TestResult::error(
857                "test_integer_wraparound_keys",
858                start.elapsed(),
859                format!(
860                    "Row {}: rval {} doesn't match expected {} for key {}",
861                    i, rval, expected_rval, key
862                ),
863            );
864        }
865
866        // Verify key is in right table
867        if !right_key_set.contains(&key) {
868            return TestResult::error(
869                "test_integer_wraparound_keys",
870                start.elapsed(),
871                format!("Row {}: key {} is not in right table", i, key),
872            );
873        }
874    }
875
876    // Verify all right keys that should match are present in results
877    let joined_key_set: HashSet<u32> = joined_keys.iter().copied().collect();
878    let left_key_set: HashSet<u32> = left_keys.iter().copied().collect();
879    for &rkey in &right_keys {
880        if left_key_set.contains(&rkey) && !joined_key_set.contains(&rkey) {
881            return TestResult::error(
882                "test_integer_wraparound_keys",
883                start.elapsed(),
884                format!("Key {} should appear in join result but doesn't", rkey),
885            );
886        }
887    }
888
889    // Specifically verify wraparound keys are handled correctly
890    let critical_keys = [u32::MAX, 0, 0x8000_0000];
891    for &ckey in &critical_keys {
892        if left_key_set.contains(&ckey) && right_key_set.contains(&ckey) {
893            if !joined_key_set.contains(&ckey) {
894                return TestResult::error(
895                    "test_integer_wraparound_keys",
896                    start.elapsed(),
897                    format!(
898                        "Critical key {} (0x{:08X}) missing from join result",
899                        ckey, ckey
900                    ),
901                );
902            }
903        }
904    }
905
906    if let Err(e) = ctx.sync_and_check() {
907        return TestResult::error(
908            "test_integer_wraparound_keys",
909            start.elapsed(),
910            format!("Sync failed: {}", e),
911        );
912    }
913
914    TestResult::passed("test_integer_wraparound_keys", start.elapsed())
915}