1use crate::harness::{CategoryResult, TestContext, TestResult};
7use std::time::Instant;
8use xlog_core::{ScalarType, Schema};
9
10pub fn run_all(ctx: &TestContext) -> CategoryResult {
12 let mut results = CategoryResult::new("c08_synchronization");
13 let start = Instant::now();
14
15 results.add_result(test_hash_join_atomics(ctx));
16 results.add_result(test_filter_scan_sync(ctx));
17 results.add_result(test_sort_barrier_correctness(ctx));
18 results.add_result(test_dedup_atomic_marking(ctx));
19 results.add_result(test_concurrent_operations(ctx));
20
21 results.set_duration(start.elapsed());
22 results
23}
24
25fn test_hash_join_atomics(ctx: &TestContext) -> TestResult {
30 let start = Instant::now();
31
32 let left_schema = Schema::new(vec![
33 ("key".to_string(), ScalarType::U32),
34 ("lval".to_string(), ScalarType::U32),
35 ]);
36 let right_schema = Schema::new(vec![
37 ("key".to_string(), ScalarType::U32),
38 ("rval".to_string(), ScalarType::U32),
39 ]);
40
41 const LEFT_SIZE: usize = 10000;
43 let left_keys: Vec<u32> = (0..LEFT_SIZE as u32).collect();
44 let left_vals: Vec<u32> = (0..LEFT_SIZE as u32).map(|i| i * 10).collect();
45
46 const RIGHT_SIZE: usize = 5000;
48 let right_keys: Vec<u32> = (0..RIGHT_SIZE as u32).map(|i| i * 2).collect();
49 let right_vals: Vec<u32> = (0..RIGHT_SIZE as u32).map(|i| i * 100).collect();
50
51 let left_buffer = match ctx
52 .provider
53 .create_buffer_from_u32_columns(&[&left_keys, &left_vals], left_schema.clone())
54 {
55 Ok(buf) => buf,
56 Err(e) => {
57 return TestResult::error(
58 "test_hash_join_atomics",
59 start.elapsed(),
60 format!("Failed to create left buffer: {}", e),
61 )
62 }
63 };
64
65 let right_buffer = match ctx
66 .provider
67 .create_buffer_from_u32_columns(&[&right_keys, &right_vals], right_schema.clone())
68 {
69 Ok(buf) => buf,
70 Err(e) => {
71 return TestResult::error(
72 "test_hash_join_atomics",
73 start.elapsed(),
74 format!("Failed to create right buffer: {}", e),
75 )
76 }
77 };
78
79 let joined = match ctx
81 .provider
82 .hash_join(&left_buffer, &right_buffer, &[0], &[0])
83 {
84 Ok(j) => j,
85 Err(e) => {
86 return TestResult::error(
87 "test_hash_join_atomics",
88 start.elapsed(),
89 format!("Hash join failed: {}", e),
90 )
91 }
92 };
93
94 let expected_matches = RIGHT_SIZE;
96 if ctx.device_row_count(&joined) != expected_matches as u64 {
97 return TestResult::error(
98 "test_hash_join_atomics",
99 start.elapsed(),
100 format!(
101 "Join returned {} rows, expected {}",
102 ctx.device_row_count(&joined),
103 expected_matches
104 ),
105 );
106 }
107
108 let joined_keys = match ctx.provider.download_column::<u32>(&joined, 0) {
110 Ok(d) => d,
111 Err(e) => {
112 return TestResult::error(
113 "test_hash_join_atomics",
114 start.elapsed(),
115 format!("Failed to download joined keys: {}", e),
116 )
117 }
118 };
119
120 let joined_lvals = match ctx.provider.download_column::<u32>(&joined, 1) {
121 Ok(d) => d,
122 Err(e) => {
123 return TestResult::error(
124 "test_hash_join_atomics",
125 start.elapsed(),
126 format!("Failed to download joined lvals: {}", e),
127 )
128 }
129 };
130
131 let joined_rvals = match ctx.provider.download_column::<u32>(&joined, 2) {
132 Ok(d) => d,
133 Err(e) => {
134 return TestResult::error(
135 "test_hash_join_atomics",
136 start.elapsed(),
137 format!("Failed to download joined rvals: {}", e),
138 )
139 }
140 };
141
142 for i in 0..ctx.device_row_count(&joined) as usize {
144 let key = joined_keys[i];
145 let lval = joined_lvals[i];
146 let rval = joined_rvals[i];
147
148 if key % 2 != 0 {
150 return TestResult::error(
151 "test_hash_join_atomics",
152 start.elapsed(),
153 format!("Row {}: key {} should be even", i, key),
154 );
155 }
156
157 let expected_lval = key * 10;
159 if lval != expected_lval {
160 return TestResult::error(
161 "test_hash_join_atomics",
162 start.elapsed(),
163 format!(
164 "Row {}: lval {} doesn't match expected {} for key {}",
165 i, lval, expected_lval, key
166 ),
167 );
168 }
169
170 let expected_rval = (key / 2) * 100;
172 if rval != expected_rval {
173 return TestResult::error(
174 "test_hash_join_atomics",
175 start.elapsed(),
176 format!(
177 "Row {}: rval {} doesn't match expected {} for key {}",
178 i, rval, expected_rval, key
179 ),
180 );
181 }
182 }
183
184 if let Err(e) = ctx.sync_and_check() {
185 return TestResult::error(
186 "test_hash_join_atomics",
187 start.elapsed(),
188 format!("Sync failed: {}", e),
189 );
190 }
191
192 TestResult::passed("test_hash_join_atomics", start.elapsed())
193}
194
195fn test_filter_scan_sync(ctx: &TestContext) -> TestResult {
200 let start = Instant::now();
201 let schema = Schema::new(vec![("val".to_string(), ScalarType::U32)]);
202
203 type SizePredicate = (usize, Box<dyn Fn(usize) -> bool>);
205 let test_cases: Vec<SizePredicate> = vec![
206 (1000, Box::new(|i| i % 2 == 0)), (10000, Box::new(|i| i % 3 == 0)), (50000, Box::new(|i| i < 25000)), (100000, Box::new(|i| i % 7 == 0)), (65536, Box::new(|i| (i / 256) % 2 == 0)), ];
213
214 for (size, predicate) in test_cases {
215 let data: Vec<u32> = (0..size as u32).collect();
217
218 let buffer = match ctx
219 .provider
220 .create_buffer_from_slice::<u32>(&data, schema.clone())
221 {
222 Ok(buf) => buf,
223 Err(e) => {
224 return TestResult::error(
225 "test_filter_scan_sync",
226 start.elapsed(),
227 format!("Size {}: failed to create buffer: {}", size, e),
228 )
229 }
230 };
231
232 let mask: Vec<u8> = (0..size)
234 .map(|i| if predicate(i) { 1 } else { 0 })
235 .collect();
236 let expected_count: usize = mask.iter().map(|&m| m as usize).sum();
237
238 let filtered = match ctx.provider.filter_by_mask(&buffer, &mask) {
239 Ok(f) => f,
240 Err(e) => {
241 return TestResult::error(
242 "test_filter_scan_sync",
243 start.elapsed(),
244 format!("Size {}: filter failed: {}", size, e),
245 )
246 }
247 };
248
249 if ctx.device_row_count(&filtered) != expected_count as u64 {
251 return TestResult::error(
252 "test_filter_scan_sync",
253 start.elapsed(),
254 format!(
255 "Size {}: filter returned {} rows, expected {}",
256 size,
257 ctx.device_row_count(&filtered),
258 expected_count
259 ),
260 );
261 }
262
263 let filtered_data = match ctx.provider.download_column::<u32>(&filtered, 0) {
265 Ok(d) => d,
266 Err(e) => {
267 return TestResult::error(
268 "test_filter_scan_sync",
269 start.elapsed(),
270 format!("Size {}: failed to download: {}", size, e),
271 )
272 }
273 };
274
275 let mut expected_idx = 0;
277 for i in 0..size {
278 if predicate(i) {
279 if expected_idx >= filtered_data.len() {
280 return TestResult::error(
281 "test_filter_scan_sync",
282 start.elapsed(),
283 format!(
284 "Size {}: filtered data too short at expected index {}",
285 size, expected_idx
286 ),
287 );
288 }
289 if filtered_data[expected_idx] != i as u32 {
290 return TestResult::error(
291 "test_filter_scan_sync",
292 start.elapsed(),
293 format!(
294 "Size {}: filtered[{}] = {}, expected {}",
295 size, expected_idx, filtered_data[expected_idx], i
296 ),
297 );
298 }
299 expected_idx += 1;
300 }
301 }
302 }
303
304 if let Err(e) = ctx.sync_and_check() {
305 return TestResult::error(
306 "test_filter_scan_sync",
307 start.elapsed(),
308 format!("Sync failed: {}", e),
309 );
310 }
311
312 TestResult::passed("test_filter_scan_sync", start.elapsed())
313}
314
315fn test_sort_barrier_correctness(ctx: &TestContext) -> TestResult {
321 let start = Instant::now();
322 let schema = Schema::new(vec![
323 ("key".to_string(), ScalarType::U32),
324 ("val".to_string(), ScalarType::U32),
325 ]);
326
327 let test_patterns: Vec<(&str, Vec<u32>)> = vec![
329 ("reverse", (0..10000u32).rev().collect()),
330 (
331 "alternating",
332 (0..10000u32)
333 .map(|i| if i % 2 == 0 { i } else { 10000 - i })
334 .collect(),
335 ),
336 ("sawtooth", (0..10000u32).map(|i| i % 100).collect()),
337 (
338 "random_lcg",
339 (0..10000usize)
340 .map(|i| ((i * 1103515245 + 12345) % 10000) as u32)
341 .collect(),
342 ),
343 (
344 "blocks_reversed",
345 (0..10000u32)
346 .map(|i| {
347 let block = i / 256;
348 let offset = i % 256;
349 block * 256 + (255 - offset)
350 })
351 .collect(),
352 ),
353 ];
354
355 for (name, keys) in test_patterns {
356 let size = keys.len();
357 let vals: Vec<u32> = (0..size as u32).collect();
358
359 let buffer = match ctx
360 .provider
361 .create_buffer_from_u32_columns(&[&keys, &vals], schema.clone())
362 {
363 Ok(buf) => buf,
364 Err(e) => {
365 return TestResult::error(
366 "test_sort_barrier_correctness",
367 start.elapsed(),
368 format!("Pattern {}: failed to create buffer: {}", name, e),
369 )
370 }
371 };
372
373 let sorted = match ctx.provider.sort(&buffer, &[0]) {
375 Ok(s) => s,
376 Err(e) => {
377 return TestResult::error(
378 "test_sort_barrier_correctness",
379 start.elapsed(),
380 format!("Pattern {}: sort failed: {}", name, e),
381 )
382 }
383 };
384
385 if ctx.device_row_count(&sorted) != size as u64 {
387 return TestResult::error(
388 "test_sort_barrier_correctness",
389 start.elapsed(),
390 format!(
391 "Pattern {}: sort returned {} rows, expected {}",
392 name,
393 ctx.device_row_count(&sorted),
394 size
395 ),
396 );
397 }
398
399 let sorted_keys = match ctx.provider.download_column::<u32>(&sorted, 0) {
401 Ok(d) => d,
402 Err(e) => {
403 return TestResult::error(
404 "test_sort_barrier_correctness",
405 start.elapsed(),
406 format!("Pattern {}: failed to download sorted keys: {}", name, e),
407 )
408 }
409 };
410
411 for i in 1..size {
413 if sorted_keys[i] < sorted_keys[i - 1] {
414 return TestResult::error(
415 "test_sort_barrier_correctness",
416 start.elapsed(),
417 format!(
418 "Pattern {}: sort order incorrect at {}: {} > {}",
419 name,
420 i,
421 sorted_keys[i - 1],
422 sorted_keys[i]
423 ),
424 );
425 }
426 }
427
428 let mut original_sorted = keys.clone();
430 original_sorted.sort();
431 if sorted_keys != original_sorted {
432 return TestResult::error(
433 "test_sort_barrier_correctness",
434 start.elapsed(),
435 format!("Pattern {}: keys not preserved through sort", name),
436 );
437 }
438 }
439
440 if let Err(e) = ctx.sync_and_check() {
441 return TestResult::error(
442 "test_sort_barrier_correctness",
443 start.elapsed(),
444 format!("Sync failed: {}", e),
445 );
446 }
447
448 TestResult::passed("test_sort_barrier_correctness", start.elapsed())
449}
450
451fn test_dedup_atomic_marking(ctx: &TestContext) -> TestResult {
456 let start = Instant::now();
457 let schema = Schema::new(vec![
458 ("key".to_string(), ScalarType::U32),
459 ("val".to_string(), ScalarType::U32),
460 ]);
461
462 let test_cases: Vec<(&str, Vec<u32>, Vec<u32>)> = vec![
464 ("all_same", vec![42; 10000], (0..10000u32).collect()),
466 (
467 "pairs",
468 (0..5000u32).flat_map(|i| vec![i, i]).collect(),
469 (0..10000u32).collect(),
470 ),
471 (
472 "triples",
473 (0..3333u32)
474 .flat_map(|i| vec![i, i, i])
475 .take(9999)
476 .collect(),
477 (0..9999u32).collect(),
478 ),
479 ("no_dups", (0..10000u32).collect(), (0..10000u32).collect()),
480 (
481 "many_dups",
482 (0..10000u32).map(|i| i % 100).collect(),
483 (0..10000u32).collect(),
484 ),
485 ];
486
487 for (name, keys, vals) in test_cases {
488 let buffer = match ctx
489 .provider
490 .create_buffer_from_u32_columns(&[&keys, &vals], schema.clone())
491 {
492 Ok(buf) => buf,
493 Err(e) => {
494 return TestResult::error(
495 "test_dedup_atomic_marking",
496 start.elapsed(),
497 format!("Pattern {}: failed to create buffer: {}", name, e),
498 )
499 }
500 };
501
502 let deduped = match ctx.provider.dedup(&buffer, &[0]) {
504 Ok(d) => d,
505 Err(e) => {
506 return TestResult::error(
507 "test_dedup_atomic_marking",
508 start.elapsed(),
509 format!("Pattern {}: dedup failed: {}", name, e),
510 )
511 }
512 };
513
514 let mut unique_keys: std::collections::HashSet<u32> = std::collections::HashSet::new();
516 for &k in &keys {
517 unique_keys.insert(k);
518 }
519 let expected_unique = unique_keys.len();
520
521 if ctx.device_row_count(&deduped) != expected_unique as u64 {
522 return TestResult::error(
523 "test_dedup_atomic_marking",
524 start.elapsed(),
525 format!(
526 "Pattern {}: dedup returned {} rows, expected {}",
527 name,
528 ctx.device_row_count(&deduped),
529 expected_unique
530 ),
531 );
532 }
533
534 let deduped_keys = match ctx.provider.download_column::<u32>(&deduped, 0) {
536 Ok(d) => d,
537 Err(e) => {
538 return TestResult::error(
539 "test_dedup_atomic_marking",
540 start.elapsed(),
541 format!("Pattern {}: failed to download deduped keys: {}", name, e),
542 )
543 }
544 };
545
546 let mut seen_keys: std::collections::HashSet<u32> = std::collections::HashSet::new();
548 for &k in &deduped_keys {
549 if !seen_keys.insert(k) {
550 return TestResult::error(
551 "test_dedup_atomic_marking",
552 start.elapsed(),
553 format!("Pattern {}: duplicate key {} in dedup result", name, k),
554 );
555 }
556 }
557
558 if seen_keys != unique_keys {
560 return TestResult::error(
561 "test_dedup_atomic_marking",
562 start.elapsed(),
563 format!("Pattern {}: dedup result missing some unique keys", name),
564 );
565 }
566 }
567
568 if let Err(e) = ctx.sync_and_check() {
569 return TestResult::error(
570 "test_dedup_atomic_marking",
571 start.elapsed(),
572 format!("Sync failed: {}", e),
573 );
574 }
575
576 TestResult::passed("test_dedup_atomic_marking", start.elapsed())
577}
578
579fn test_concurrent_operations(ctx: &TestContext) -> TestResult {
584 let start = Instant::now();
585 let schema = Schema::new(vec![("val".to_string(), ScalarType::U32)]);
586
587 const NUM_BUFFERS: usize = 5;
589 const BUFFER_SIZE: usize = 10000;
590
591 let mut buffers = Vec::with_capacity(NUM_BUFFERS);
592 let mut original_data = Vec::with_capacity(NUM_BUFFERS);
593
594 for i in 0..NUM_BUFFERS {
595 let data: Vec<u32> = (0..BUFFER_SIZE)
597 .map(|j| ((j * (i + 1) * 7 + i * 13) % BUFFER_SIZE) as u32)
598 .collect();
599
600 let buffer = match ctx
601 .provider
602 .create_buffer_from_slice::<u32>(&data, schema.clone())
603 {
604 Ok(buf) => buf,
605 Err(e) => {
606 return TestResult::error(
607 "test_concurrent_operations",
608 start.elapsed(),
609 format!("Failed to create buffer {}: {}", i, e),
610 )
611 }
612 };
613
614 original_data.push(data);
615 buffers.push(buffer);
616 }
617
618 let mut results = Vec::with_capacity(NUM_BUFFERS);
620
621 let sorted0 = match ctx.provider.sort(&buffers[0], &[0]) {
623 Ok(s) => s,
624 Err(e) => {
625 return TestResult::error(
626 "test_concurrent_operations",
627 start.elapsed(),
628 format!("Sort of buffer 0 failed: {}", e),
629 )
630 }
631 };
632 results.push(("sort", sorted0));
633
634 let mask1: Vec<u8> = (0..BUFFER_SIZE)
636 .map(|i| if i < BUFFER_SIZE / 2 { 1 } else { 0 })
637 .collect();
638 let filtered1 = match ctx.provider.filter_by_mask(&buffers[1], &mask1) {
639 Ok(f) => f,
640 Err(e) => {
641 return TestResult::error(
642 "test_concurrent_operations",
643 start.elapsed(),
644 format!("Filter of buffer 1 failed: {}", e),
645 )
646 }
647 };
648 results.push(("filter", filtered1));
649
650 let sorted2 = match ctx.provider.sort(&buffers[2], &[0]) {
652 Ok(s) => s,
653 Err(e) => {
654 return TestResult::error(
655 "test_concurrent_operations",
656 start.elapsed(),
657 format!("Sort of buffer 2 failed: {}", e),
658 )
659 }
660 };
661 results.push(("sort", sorted2));
662
663 let mask3: Vec<u8> = (0..BUFFER_SIZE)
665 .map(|i| if i % 2 == 0 { 1 } else { 0 })
666 .collect();
667 let filtered3 = match ctx.provider.filter_by_mask(&buffers[3], &mask3) {
668 Ok(f) => f,
669 Err(e) => {
670 return TestResult::error(
671 "test_concurrent_operations",
672 start.elapsed(),
673 format!("Filter of buffer 3 failed: {}", e),
674 )
675 }
676 };
677 results.push(("filter", filtered3));
678
679 let sorted4 = match ctx.provider.sort(&buffers[4], &[0]) {
681 Ok(s) => s,
682 Err(e) => {
683 return TestResult::error(
684 "test_concurrent_operations",
685 start.elapsed(),
686 format!("Sort of buffer 4 failed: {}", e),
687 )
688 }
689 };
690 results.push(("sort", sorted4));
691
692 for &idx in &[0usize, 2, 4] {
695 let (op, ref result) = results[idx];
696 assert!(op == "sort");
697
698 if ctx.device_row_count(result) != BUFFER_SIZE as u64 {
699 return TestResult::error(
700 "test_concurrent_operations",
701 start.elapsed(),
702 format!(
703 "Buffer {}: {} returned {} rows, expected {}",
704 idx,
705 op,
706 ctx.device_row_count(result),
707 BUFFER_SIZE
708 ),
709 );
710 }
711
712 let sorted_data = match ctx.provider.download_column::<u32>(result, 0) {
713 Ok(d) => d,
714 Err(e) => {
715 return TestResult::error(
716 "test_concurrent_operations",
717 start.elapsed(),
718 format!("Buffer {}: failed to download: {}", idx, e),
719 )
720 }
721 };
722
723 for i in 1..BUFFER_SIZE {
725 if sorted_data[i] < sorted_data[i - 1] {
726 return TestResult::error(
727 "test_concurrent_operations",
728 start.elapsed(),
729 format!(
730 "Buffer {}: sort order incorrect at {}: {} > {}",
731 idx,
732 i,
733 sorted_data[i - 1],
734 sorted_data[i]
735 ),
736 );
737 }
738 }
739
740 let mut expected = original_data[idx].clone();
742 expected.sort();
743 if sorted_data != expected {
744 return TestResult::error(
745 "test_concurrent_operations",
746 start.elapsed(),
747 format!("Buffer {}: sorted data doesn't match expected", idx),
748 );
749 }
750 }
751
752 let (op1, ref result1) = results[1];
755 assert!(op1 == "filter");
756 if ctx.device_row_count(result1) != (BUFFER_SIZE / 2) as u64 {
757 return TestResult::error(
758 "test_concurrent_operations",
759 start.elapsed(),
760 format!(
761 "Buffer 1: filter returned {} rows, expected {}",
762 ctx.device_row_count(result1),
763 BUFFER_SIZE / 2
764 ),
765 );
766 }
767
768 let (op3, ref result3) = results[3];
770 assert!(op3 == "filter");
771 if ctx.device_row_count(result3) != BUFFER_SIZE.div_ceil(2) as u64 {
772 return TestResult::error(
773 "test_concurrent_operations",
774 start.elapsed(),
775 format!(
776 "Buffer 3: filter returned {} rows, expected {}",
777 ctx.device_row_count(result3),
778 BUFFER_SIZE.div_ceil(2)
779 ),
780 );
781 }
782
783 for i in 0..NUM_BUFFERS {
785 let current_data = match ctx.provider.download_column::<u32>(&buffers[i], 0) {
786 Ok(d) => d,
787 Err(e) => {
788 return TestResult::error(
789 "test_concurrent_operations",
790 start.elapsed(),
791 format!("Failed to verify buffer {}: {}", i, e),
792 )
793 }
794 };
795
796 if current_data != original_data[i] {
797 return TestResult::error(
798 "test_concurrent_operations",
799 start.elapsed(),
800 format!("Buffer {} was modified by operations", i),
801 );
802 }
803 }
804
805 if let Err(e) = ctx.sync_and_check() {
806 return TestResult::error(
807 "test_concurrent_operations",
808 start.elapsed(),
809 format!("Sync failed: {}", e),
810 );
811 }
812
813 TestResult::passed("test_concurrent_operations", start.elapsed())
814}